diff --git a/Readme.md b/Readme.md index 7653282..5e5cb9b 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,10 @@ -Bitbanged DVI on the RP2040 Microcontroller -=========================================== +Bitbanged DVI on the RP2040 Microcontroller with HDMI and audio support +================================================================================== +Big Kudos to @shuichitakano to fix the audio support + +Using https://github.com/shuichitakano/pico_lib C++ implementation of the HDMI with audio support +For picoDVI from https://github.com/Wren6991/PicoDVI +DVI start/stop works, audio works with some limitations of cpu usage, if line or pixel doubling is enabled works ![](img/mountains.jpg) diff --git a/software/Readme.md b/software/Readme.md index e229f23..a460e69 100644 --- a/software/Readme.md +++ b/software/Readme.md @@ -3,6 +3,49 @@ Building You need to have the Pico SDK installed, as well as a recent CMake and arm-none-eabi-gcc. +# Setup +- Download the pico-sdk, pico-extras, and pico-playground repositories +- Define PICO_SDK_PATH in your ~/.bashrc• +- Download the pico-sdk repository + +# Build +``` +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Debug +``` +Go to the specific folder and + +`make -j4` + +# Visual Studio Code integration +Install the extensions as explained in the [Pico Getting started manual](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf) + +Or just download [VScode](https://code.visualstudio.com/Download) and add the following extension + +``` +code --install-extension marus25.cortex-debug +code --install-extension ms-vscode.cmake-tools +code --install-extension ms-vscode.cpptools +``` + +# Debug +Install open ocd as explained in the [Pico Getting started manual section Installing OpenOCD](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf) + +#Using Pico probe +On the same document check **Appendix A: Using Picoprobe** + +Open deploy and run +``` +openocd -f interface/picoprobe.cfg -f target/rp2040.cfg -c "program verify reset exit" +``` + +# Read RP2040 to uf2 +``` +picotool save -a pico_rgb2hdmi_xyz.uf2 +``` + +# Above this line is the original readme ```bash mkdir build diff --git a/software/apps/CMakeLists.txt b/software/apps/CMakeLists.txt index 0c95dfb..9bfa133 100644 --- a/software/apps/CMakeLists.txt +++ b/software/apps/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(hello_dvi) add_subdirectory(mandelbrot) add_subdirectory(moon) add_subdirectory(sprite_bounce) +add_subdirectory(sprite_bounce_audio) add_subdirectory(terminal) add_subdirectory(tiles) add_subdirectory(tiles_and_sprites) diff --git a/software/apps/sprite_bounce_audio/CMakeLists.txt b/software/apps/sprite_bounce_audio/CMakeLists.txt new file mode 100644 index 0000000..c25d25a --- /dev/null +++ b/software/apps/sprite_bounce_audio/CMakeLists.txt @@ -0,0 +1,24 @@ +# Replace TMDS with 10 bit UART (same baud rate): +# add_definitions(-DDVI_SERIAL_DEBUG=1) +# add_definitions(-DRUN_FROM_CRYSTAL) + +add_executable(sprite_bounce_audio + main.c +) + +target_compile_options(sprite_bounce_audio PRIVATE -Wall) + +target_compile_definitions(sprite_bounce_audio PRIVATE + DVI_DEFAULT_SERIAL_CONFIG=${DVI_DEFAULT_SERIAL_CONFIG} + ) + +target_link_libraries(sprite_bounce_audio + pico_stdlib + pico_multicore + pico_util + libdvi + libsprite +) + +# create map/bin/hex file etc. +pico_add_extra_outputs(sprite_bounce_audio) diff --git a/software/apps/sprite_bounce_audio/main.c b/software/apps/sprite_bounce_audio/main.c new file mode 100644 index 0000000..0bd527d --- /dev/null +++ b/software/apps/sprite_bounce_audio/main.c @@ -0,0 +1,235 @@ +#include +#include +#include "pico/stdlib.h" +#include "pico/multicore.h" +#include "hardware/clocks.h" +#include "hardware/irq.h" +#include "hardware/sync.h" +#include "hardware/gpio.h" +#include "hardware/vreg.h" +#include "pico/sem.h" + +#include "dvi.h" +#include "dvi_serialiser.h" +#include "common_dvi_pin_configs.h" +#include "sprite.h" + +// Pick one: +#define MODE_640x480_60Hz +// #define MODE_800x480_60Hz +// #define MODE_800x600_60Hz +// #define MODE_960x540p_60Hz +// #define MODE_1280x720_30Hz + +#include "raspberry_128x128_rgab5515.h" +#include "eben_128x128_rgab5515.h" + +#if defined(MODE_640x480_60Hz) +// DVDD 1.2V (1.1V seems ok too) +#define FRAME_WIDTH 320 +#define FRAME_HEIGHT 240 +#define VREG_VSEL VREG_VOLTAGE_1_20 +#define DVI_TIMING dvi_timing_640x480p_60hz + +#elif defined(MODE_800x480_60Hz) +#define FRAME_WIDTH 400 +#define FRAME_HEIGHT 240 +#define VREG_VSEL VREG_VOLTAGE_1_20 +#define DVI_TIMING dvi_timing_800x480p_60hz + +#elif defined(MODE_800x600_60Hz) +// DVDD 1.3V, going downhill with a tailwind +#define FRAME_WIDTH 400 +#define FRAME_HEIGHT 300 +#define VREG_VSEL VREG_VOLTAGE_1_30 +#define DVI_TIMING dvi_timing_800x600p_60hz + +#elif defined(MODE_960x540p_60Hz) +// DVDD 1.25V (slower silicon may need the full 1.3, or just not work) +// Frame resolution is almost the same as a PSP :) +#define FRAME_WIDTH 480 +#define FRAME_HEIGHT 270 +#define VREG_VSEL VREG_VOLTAGE_1_25 +#define DVI_TIMING dvi_timing_960x540p_60hz + +#elif defined(MODE_1280x720_30Hz) +// 1280x720p 30 Hz (nonstandard) +// DVDD 1.25V (slower silicon may need the full 1.3, or just not work) +#define FRAME_WIDTH 640 +#define FRAME_HEIGHT 360 +#define VREG_VSEL VREG_VOLTAGE_1_25 +#define DVI_TIMING dvi_timing_1280x720p_30hz + +#else +#error "Select a video mode!" +#endif + +#define N_BERRIES 65 +#define LED_PIN 21 + +struct dvi_inst dvi0; + +void core1_main() { + dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0); + while (queue_is_empty(&dvi0.q_colour_valid)) + __wfe(); + dvi_start(&dvi0); + dvi_scanbuf_main_16bpp(&dvi0); + __builtin_unreachable(); +} + +static inline int clip(int x, int min, int max) { + return x < min ? min : x > max ? max : x; +} + +#define N_SCANLINE_BUFFERS 4 +uint16_t static_scanbuf[N_SCANLINE_BUFFERS][FRAME_WIDTH]; + +sprite_t berry[N_BERRIES]; +int vx[N_BERRIES]; +int vy[N_BERRIES]; +int vt[N_BERRIES]; +uint8_t theta[N_BERRIES]; +affine_transform_t atrans[N_BERRIES]; + +const int xmin = -100; +const int xmax = FRAME_WIDTH - 30; +const int ymin = -100; +const int ymax = FRAME_HEIGHT - 30; +const int vmax = 4; + +//Audio Related +#define AUDIO_BUFFER_SIZE 256 +audio_sample_t audio_buffer[AUDIO_BUFFER_SIZE]; +struct repeating_timer audio_timer; +const int16_t sine[32] = { + 0x8000,0x98f8,0xb0fb,0xc71c,0xda82,0xea6d,0xf641,0xfd89, + 0xffff,0xfd89,0xf641,0xea6d,0xda82,0xc71c,0xb0fb,0x98f8, + 0x8000,0x6707,0x4f04,0x38e3,0x257d,0x1592,0x9be,0x276, + 0x0,0x276,0x9be,0x1592,0x257d,0x38e3,0x4f04,0x6707 +}; + +bool audio_timer_callback(struct repeating_timer *t) { + int size = get_write_size(&dvi0.audio_ring, false); + audio_sample_t *audio_ptr = get_write_pointer(&dvi0.audio_ring); + audio_sample_t sample; + static uint sample_count = 0; + for (int cnt = 0; cnt < size; cnt++) { + sample.channels[0] = sine[sample_count % 32]; + sample.channels[1] = sine[sample_count % 32]; + *audio_ptr++ = sample; + sample_count++; + } + increase_write_pointer(&dvi0.audio_ring, size); + + return true; +} + + +void __not_in_flash("render") render_loop() { + uint heartbeat = 0; + uint frame_ctr = 0; + while (1) { + if (++heartbeat >= 30) { + heartbeat = 0; + gpio_xor_mask(1u << LED_PIN); + } + for (uint y = 0; y < FRAME_HEIGHT; ++y) { + uint16_t *pixbuf; + queue_remove_blocking(&dvi0.q_colour_free, &pixbuf); + // sprite_blit16(pixbuf, (const uint16_t *)testcard_320x240 + (y + frame_ctr / 2) % 240 * FRAME_WIDTH, 320); + sprite_fill16(pixbuf, 0x07ff, FRAME_WIDTH); + for (int i = 0; i < N_BERRIES; ++i) + // sprite_asprite16(pixbuf, &berry[i], atrans[i], y, FRAME_WIDTH); + sprite_sprite16(pixbuf, &berry[i], y, FRAME_WIDTH); + queue_add_blocking(&dvi0.q_colour_valid, &pixbuf); + } + // Update during vblank + for (int i = 0; i < N_BERRIES; ++i) { + berry[i].x += vx[i]; + berry[i].y += vy[i]; + theta[i] += vt[i]; + affine_identity(atrans[i]); + affine_scale(atrans[i], 7 * AF_ONE / 8, 7 * AF_ONE / 8); + affine_translate(atrans[i], -56, -56); + affine_rotate(atrans[i], theta[i]); + affine_translate(atrans[i], 60, 60); + int xclip = clip(berry[i].x, xmin, xmax); + int yclip = clip(berry[i].y, ymin, ymax); + if (xclip != berry[i].x || yclip != berry[i].y) { + berry[i].x = xclip; + berry[i].y = yclip; + vx[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1); + vy[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1); + vt[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1); + berry[i].hflip = vx[i] < 0; + berry[i].vflip = vy[i] < 0; + } + } + ++frame_ctr; + } +} + + + + +int main() { + vreg_set_voltage(VREG_VSEL); + sleep_ms(10); +#ifdef RUN_FROM_CRYSTAL + // Slow everything down uniformly, so signals are probeable but the code runs + // identically (note this actually uses the PLL with low feedback and max PD1/PD2) + set_sys_clock_khz(12000, true); +#else + // Run system at TMDS bit clock + set_sys_clock_khz(DVI_TIMING.bit_clk_khz, true); +#endif + + setup_default_uart(); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + printf("Configuring DVI\n"); + + dvi0.timing = &DVI_TIMING; + dvi0.ser_cfg = DVI_DEFAULT_SERIAL_CONFIG; + dvi_init(&dvi0, next_striped_spin_lock_num(), next_striped_spin_lock_num()); + + // HDMI Audio related + dvi_get_blank_settings(&dvi0)->top = 4 * 2; + dvi_get_blank_settings(&dvi0)->bottom = 4 * 2; + dvi_audio_sample_buffer_set(&dvi0, audio_buffer, AUDIO_BUFFER_SIZE); + dvi_set_audio_freq(&dvi0, 44100, 28000, 6272); + add_repeating_timer_ms(2, audio_timer_callback, NULL, &audio_timer); + + printf("Core 1 start\n"); + multicore_launch_core1(core1_main); + + printf("Allocating scanline buffers\n"); + for (int i = 0; i < N_SCANLINE_BUFFERS; ++i) { + void *bufptr = &static_scanbuf[i]; + queue_add_blocking((void*)&dvi0.q_colour_free, &bufptr); + } + + for (int i = 0; i < N_BERRIES; ++i) { + berry[i].x = rand() % (xmax - xmin + 1) + xmin; + berry[i].y = rand() % (ymax - ymin + 1) + ymin; + berry[i].img = i % 2 ? eben_128x128 : raspberry_128x128; + berry[i].log_size = 7; + berry[i].has_opacity_metadata = true; // Much faster non-AT blitting + berry[i].hflip = false; + berry[i].vflip = false; + vx[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1); + vy[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1); + vt[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1); + theta[i] = 0; + affine_identity(atrans[i]); + } + + // Core 1 will fire up the DVI once it sees the first colour buffer has been rendered + printf("Start rendering\n"); + render_loop(); + __builtin_unreachable(); +} + diff --git a/software/include/common_dvi_pin_configs.h b/software/include/common_dvi_pin_configs.h index b3893a3..187dcff 100644 --- a/software/include/common_dvi_pin_configs.h +++ b/software/include/common_dvi_pin_configs.h @@ -61,13 +61,22 @@ static const struct dvi_serialiser_cfg micromod_cfg = { }; // Pico DVI Sock (small hat on the bottom) which solders to the end of a Pico +// static const struct dvi_serialiser_cfg pico_sock_cfg = { +// .pio = DVI_DEFAULT_PIO_INST, +// .sm_tmds = {0, 1, 2}, +// .pins_tmds = {12, 18, 16}, +// .pins_clk = 14, +// .invert_diffpairs = false +// }; + +// pico-RGB2HDMI static const struct dvi_serialiser_cfg pico_sock_cfg = { - .pio = DVI_DEFAULT_PIO_INST, - .sm_tmds = {0, 1, 2}, - .pins_tmds = {12, 18, 16}, - .pins_clk = 14, - .invert_diffpairs = false -}; + .pio = pio0, + .sm_tmds = {0, 1, 2}, + .pins_tmds = {5, 7, 9}, + .pins_clk = 3, + .invert_diffpairs = true + }; // The HDMI socket on Pimoroni Pico Demo HDMI // (we would talk about rev B if we had a rev B...) diff --git a/software/libdvi/CMakeLists.txt b/software/libdvi/CMakeLists.txt index 7c52661..a147bd3 100644 --- a/software/libdvi/CMakeLists.txt +++ b/software/libdvi/CMakeLists.txt @@ -17,6 +17,10 @@ target_sources(libdvi INTERFACE ${CMAKE_CURRENT_LIST_DIR}/tmds_table.h ${CMAKE_CURRENT_LIST_DIR}/tmds_table_fullres.h ${CMAKE_CURRENT_LIST_DIR}/util_queue_u32_inline.h + ${CMAKE_CURRENT_LIST_DIR}/data_packet.c + ${CMAKE_CURRENT_LIST_DIR}/data_packet.h + ${CMAKE_CURRENT_LIST_DIR}/audio_ring.c + ${CMAKE_CURRENT_LIST_DIR}/audio_ring.h ) target_include_directories(libdvi INTERFACE ${CMAKE_CURRENT_LIST_DIR}) diff --git a/software/libdvi/audio_ring.c b/software/libdvi/audio_ring.c new file mode 100644 index 0000000..eea9a38 --- /dev/null +++ b/software/libdvi/audio_ring.c @@ -0,0 +1,53 @@ +#include "audio_ring.h" +#include + +void audio_ring_set(audio_ring_t *audio_ring, audio_sample_t *buffer, uint32_t size) { + assert(size > 1); + audio_ring->buffer = buffer; + audio_ring->size = size; + audio_ring->read = 0; + audio_ring->write = 0; +} + +uint32_t get_write_size(audio_ring_t *audio_ring, bool full) { + __mem_fence_acquire(); + uint32_t rp = audio_ring->read; + uint32_t wp = audio_ring->write; + if (wp < rp) { + return rp - wp - 1; + } else { + return audio_ring->size - wp + (full ? rp - 1 : (rp == 0 ? -1 : 0)); + } +} + +uint32_t get_read_size(audio_ring_t *audio_ring, bool full) { + __mem_fence_acquire(); + uint32_t rp = audio_ring->read; + uint32_t wp = audio_ring->write; + + if (wp < rp) { + return audio_ring->size - rp + (full ? wp : 0); + } else { + return wp - rp; + } +} + +void increase_write_pointer(audio_ring_t *audio_ring, uint32_t size) { + audio_ring->write = (audio_ring->write + size) & (audio_ring->size - 1); + __mem_fence_release(); +} + +void increase_read_pointer(audio_ring_t *audio_ring, uint32_t size) { + audio_ring->read = (audio_ring->read + size) & (audio_ring->size - 1); + __mem_fence_release(); +} + +void set_write_offset(audio_ring_t *audio_ring, uint32_t v) { + audio_ring->write = v; + __mem_fence_release(); +} + +void set_read_offset(audio_ring_t *audio_ring, uint32_t v) { + audio_ring->read = v; + __mem_fence_release(); +} \ No newline at end of file diff --git a/software/libdvi/audio_ring.h b/software/libdvi/audio_ring.h new file mode 100644 index 0000000..9ae3fcd --- /dev/null +++ b/software/libdvi/audio_ring.h @@ -0,0 +1,29 @@ +#ifndef AUDIO_RING_H +#define AUDIO_RING_H +#include "pico.h" + +typedef struct audio_sample { + int16_t channels[2]; +} audio_sample_t; + +typedef struct audio_ring { + audio_sample_t *buffer; + uint32_t size; + volatile uint32_t read; + volatile uint32_t write; +} audio_ring_t; + +inline audio_sample_t *get_buffer_top(audio_ring_t *audio_ring) { return audio_ring->buffer; } +inline uint32_t get_buffer_size(audio_ring_t *audio_ring) { return audio_ring->size; } +inline uint32_t get_read_offset(audio_ring_t *audio_ring) { return audio_ring->read; } +inline uint32_t get_write_offset(audio_ring_t *audio_ring) { return audio_ring->write; } +inline audio_sample_t *get_write_pointer(audio_ring_t *audio_ring) { return audio_ring->buffer + audio_ring->write; } +inline audio_sample_t *get_read_pointer(audio_ring_t *audio_ring) { return audio_ring->buffer + audio_ring->read; } +void increase_write_pointer(audio_ring_t *audio_ring, uint32_t size); +void increase_read_pointer(audio_ring_t *audio_ring, uint32_t size); +void audio_ring_set(audio_ring_t *audio_ring, audio_sample_t *buffer, uint32_t size); +uint32_t get_write_size(audio_ring_t *audio_ring, bool full); +uint32_t get_read_size(audio_ring_t *audio_ring, bool full); +void set_write_offset(audio_ring_t *audio_ring, uint32_t v); +void set_read_offset(audio_ring_t *audio_ring, uint32_t v); +#endif \ No newline at end of file diff --git a/software/libdvi/data_packet.c b/software/libdvi/data_packet.c new file mode 100644 index 0000000..db41b91 --- /dev/null +++ b/software/libdvi/data_packet.c @@ -0,0 +1,317 @@ +#include "data_packet.h" +#include + +// Compute 8 Parity Start +// Parity table is build statically with the following code +// for (int i = 0; i < 256; ++i){v_[i] = (i ^ (i >> 1) ^ (i >> 2) ^ (i >> 3) ^ (i >> 4) ^ (i >> 5) ^ (i >> 6) ^ (i >> 7)) & 1;} +const uint8_t parityTable[32] = { 0x96, 0x69, 0x69, 0x96, 0x69, 0x96, 0x96, 0x69, 0x69, 0x96, 0x96, 0x69, 0x96, 0x69, 0x69, 0x96, 0x69, 0x96, 0x96, 0x69, 0x96, 0x69, 0x69, 0x96, 0x96, 0x69, 0x69, 0x96, 0x69, 0x96, 0x96, 0x69 }; +bool __not_in_flash_func(compute8)(uint8_t index) { + return (parityTable[index / 8] >> (index % 8)) & 0x01; +} +bool __not_in_flash_func(compute8_2)(uint8_t index1, uint8_t index2) { + return compute8(index1) ^ compute8(index2); +} +bool __not_in_flash_func(compute8_3)(uint8_t index1, uint8_t index2, uint8_t index3) { + return compute8(index1) ^ compute8(index2) ^ compute8(index3); +} +// Compute 8 Parity End + +// BCH Encoding Start +const uint8_t __not_in_flash_func(bchTable_)[256] = { + 0x00, 0xd9, 0xb5, 0x6c, 0x6d, 0xb4, 0xd8, 0x01, + 0xda, 0x03, 0x6f, 0xb6, 0xb7, 0x6e, 0x02, 0xdb, + 0xb3, 0x6a, 0x06, 0xdf, 0xde, 0x07, 0x6b, 0xb2, + 0x69, 0xb0, 0xdc, 0x05, 0x04, 0xdd, 0xb1, 0x68, + 0x61, 0xb8, 0xd4, 0x0d, 0x0c, 0xd5, 0xb9, 0x60, + 0xbb, 0x62, 0x0e, 0xd7, 0xd6, 0x0f, 0x63, 0xba, + 0xd2, 0x0b, 0x67, 0xbe, 0xbf, 0x66, 0x0a, 0xd3, + 0x08, 0xd1, 0xbd, 0x64, 0x65, 0xbc, 0xd0, 0x09, + 0xc2, 0x1b, 0x77, 0xae, 0xaf, 0x76, 0x1a, 0xc3, + 0x18, 0xc1, 0xad, 0x74, 0x75, 0xac, 0xc0, 0x19, + 0x71, 0xa8, 0xc4, 0x1d, 0x1c, 0xc5, 0xa9, 0x70, + 0xab, 0x72, 0x1e, 0xc7, 0xc6, 0x1f, 0x73, 0xaa, + 0xa3, 0x7a, 0x16, 0xcf, 0xce, 0x17, 0x7b, 0xa2, + 0x79, 0xa0, 0xcc, 0x15, 0x14, 0xcd, 0xa1, 0x78, + 0x10, 0xc9, 0xa5, 0x7c, 0x7d, 0xa4, 0xc8, 0x11, + 0xca, 0x13, 0x7f, 0xa6, 0xa7, 0x7e, 0x12, 0xcb, + 0x83, 0x5a, 0x36, 0xef, 0xee, 0x37, 0x5b, 0x82, + 0x59, 0x80, 0xec, 0x35, 0x34, 0xed, 0x81, 0x58, + 0x30, 0xe9, 0x85, 0x5c, 0x5d, 0x84, 0xe8, 0x31, + 0xea, 0x33, 0x5f, 0x86, 0x87, 0x5e, 0x32, 0xeb, + 0xe2, 0x3b, 0x57, 0x8e, 0x8f, 0x56, 0x3a, 0xe3, + 0x38, 0xe1, 0x8d, 0x54, 0x55, 0x8c, 0xe0, 0x39, + 0x51, 0x88, 0xe4, 0x3d, 0x3c, 0xe5, 0x89, 0x50, + 0x8b, 0x52, 0x3e, 0xe7, 0xe6, 0x3f, 0x53, 0x8a, + 0x41, 0x98, 0xf4, 0x2d, 0x2c, 0xf5, 0x99, 0x40, + 0x9b, 0x42, 0x2e, 0xf7, 0xf6, 0x2f, 0x43, 0x9a, + 0xf2, 0x2b, 0x47, 0x9e, 0x9f, 0x46, 0x2a, 0xf3, + 0x28, 0xf1, 0x9d, 0x44, 0x45, 0x9c, 0xf0, 0x29, + 0x20, 0xf9, 0x95, 0x4c, 0x4d, 0x94, 0xf8, 0x21, + 0xfa, 0x23, 0x4f, 0x96, 0x97, 0x4e, 0x22, 0xfb, + 0x93, 0x4a, 0x26, 0xff, 0xfe, 0x27, 0x4b, 0x92, + 0x49, 0x90, 0xfc, 0x25, 0x24, 0xfd, 0x91, 0x48, +}; + +int __not_in_flash_func(encode_BCH_3)(const uint8_t *p) { + uint8_t v = bchTable_[p[0]]; + v = bchTable_[p[1] ^ v]; + v = bchTable_[p[2] ^ v]; + return v; +} + +int __not_in_flash_func(encode_BCH_7)(const uint8_t *p) { + uint8_t v = bchTable_[p[0]]; + v = bchTable_[p[1] ^ v]; + v = bchTable_[p[2] ^ v]; + v = bchTable_[p[3] ^ v]; + v = bchTable_[p[4] ^ v]; + v = bchTable_[p[5] ^ v]; + v = bchTable_[p[6] ^ v]; + return v; +} +// BCH Encoding End + +// TERC4 Start +uint16_t __not_in_flash_func(TERC4Syms_)[16] = { + 0b1010011100, + 0b1001100011, + 0b1011100100, + 0b1011100010, + 0b0101110001, + 0b0100011110, + 0b0110001110, + 0b0100111100, + 0b1011001100, + 0b0100111001, + 0b0110011100, + 0b1011000110, + 0b1010001110, + 0b1001110001, + 0b0101100011, + 0b1011000011, +}; + +uint32_t __not_in_flash_func(makeTERC4x2Char)(int i) { return TERC4Syms_[i] | (TERC4Syms_[i] << 10); } +uint32_t __not_in_flash_func(makeTERC4x2Char_2)(int i0, int i1) { return TERC4Syms_[i0] | (TERC4Syms_[i1] << 10); } +#define TERC4_0x2CharSym_ 0x000A729C // Build time generated -> makeTERC4x2Char(0); +#define dataGaurdbandSym_ 0x0004CD33 // Build time generated -> 0b0100110011'0100110011; +uint32_t __not_in_flash_func(defaultDataPacket12_)[N_DATA_ISLAND_WORDS] = { + dataGaurdbandSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + TERC4_0x2CharSym_, + dataGaurdbandSym_, +}; + +// This table is built in compilation time from a function that uses makeTERC4x2Char +uint32_t __not_in_flash_func(defaultDataPackets0_)[4][N_DATA_ISLAND_WORDS] = { + { 0xa3a8e, 0xa729c, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xa3a8e}, + { 0x9c671, 0x98e63, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x9c671}, + { 0x58d63, 0xb92e4, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x58d63}, + { 0xb0ec3, 0xb8ae2, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb0ec3} +}; + +uint32_t *__not_in_flash_func(getDefaultDataPacket0)(bool vsync, bool hsync) { + return defaultDataPackets0_[(vsync << 1) | hsync]; +} + +// TERC4 End + +void __not_in_flash_func(compute_header_parity)(data_packet_t *data_packet) { + data_packet->header[3] = encode_BCH_3(data_packet->header); +} + +void __not_in_flash_func(compute_subpacket_parity)(data_packet_t *data_packet, int i) { + data_packet->subpacket[i][7] = encode_BCH_7(data_packet->subpacket[i]); +} + +void __not_in_flash_func(compute_parity)(data_packet_t *data_packet) { + compute_header_parity(data_packet); + compute_subpacket_parity(data_packet, 0); + compute_subpacket_parity(data_packet, 1); + compute_subpacket_parity(data_packet, 2); + compute_subpacket_parity(data_packet, 3); +} + +void __not_in_flash_func(compute_info_frame_checkSum)(data_packet_t *data_packet) { + int s = 0; + for (int i = 0; i < 3; ++i) + { + s += data_packet->header[i]; + } + int n = data_packet->header[2] + 1; + for (int j = 0; j < 4; ++j) + { + for (int i = 0; i < 7 && n; ++i, --n) + { + s += data_packet->subpacket[j][i]; + } + } + data_packet->subpacket[0][0] = -s; +} + +void __not_in_flash_func(encode_header)(const data_packet_t *data_packet, uint32_t *dst, int hv, bool firstPacket) { + int hv1 = hv | 8; + if (!firstPacket) { + hv = hv1; + } + for (int i = 0; i < 4; ++i) { + uint8_t h = data_packet->header[i]; + dst[0] = makeTERC4x2Char_2(((h << 2) & 4) | hv, ((h << 1) & 4) | hv1); + dst[1] = makeTERC4x2Char_2((h & 4) | hv1, ((h >> 1) & 4) | hv1); + dst[2] = makeTERC4x2Char_2(((h >> 2) & 4) | hv1, ((h >> 3) & 4) | hv1); + dst[3] = makeTERC4x2Char_2(((h >> 4) & 4) | hv1, ((h >> 5) & 4) | hv1); + dst += 4; + hv = hv1; + } +} + +void __not_in_flash_func(encode_subpacket)(const data_packet_t *data_packet, uint32_t *dst1, uint32_t *dst2) { + for (int i = 0; i < 8; ++i) { + uint32_t v = (data_packet->subpacket[0][i] << 0) | (data_packet->subpacket[1][i] << 8) | + (data_packet->subpacket[2][i] << 16) | (data_packet->subpacket[3][i] << 24); + uint32_t t = (v ^ (v >> 7)) & 0x00aa00aa; + v = v ^ t ^ (t << 7); + t = (v ^ (v >> 14)) & 0x0000cccc; + v = v ^ t ^ (t << 14); + // 01234567 89abcdef ghijklmn opqrstuv + // 08go4cks 19hp5dlt 2aiq6emu 3bjr7fnv + dst1[0] = makeTERC4x2Char_2((v >> 0) & 15, (v >> 16) & 15); + dst1[1] = makeTERC4x2Char_2((v >> 4) & 15, (v >> 20) & 15); + dst2[0] = makeTERC4x2Char_2((v >> 8) & 15, (v >> 24) & 15); + dst2[1] = makeTERC4x2Char_2((v >> 12) & 15, (v >> 28) & 15); + dst1 += 2; + dst2 += 2; + } +} + +void __not_in_flash_func(set_null)(data_packet_t *data_packet) { + memset(data_packet, 0, sizeof(data_packet_t)); +} + +int __not_in_flash_func(set_audio_sample)(data_packet_t *data_packet, const audio_sample_t *p, int n, int frameCt) { + const int layout = 0; + const int samplePresent = (1 << n) - 1; + const int B = frameCt < 4 ? 1 << frameCt : 0; + data_packet->header[0] = 2; + data_packet->header[1] = (layout << 4) | samplePresent; + data_packet->header[2] = B << 4; + compute_header_parity(data_packet); + + for (int i = 0; i < n; ++i) + { + const int16_t l = (*p).channels[0]; + const int16_t r = (*p).channels[1]; + const uint8_t vuc = 1; // valid + uint8_t *d = data_packet->subpacket[i]; + d[0] = 0; + d[1] = l; + d[2] = l >> 8; + d[3] = 0; + d[4] = r; + d[5] = r >> 8; + + bool pl = compute8_3(d[1], d[2], vuc); + bool pr = compute8_3(d[4], d[5], vuc); + d[6] = (vuc << 0) | (pl << 3) | (vuc << 4) | (pr << 7); + compute_subpacket_parity(data_packet, i); + ++p; + // channel status (is it relevant?) + } + memset(data_packet->subpacket[n], 0, sizeof(data_packet->subpacket[0]) * (4 - n)); + // dump(); + + frameCt -= n; + if (frameCt < 0) { + frameCt += 192; + } + return frameCt; +} + +void set_audio_clock_regeneration(data_packet_t *data_packet, int cts, int n) { + data_packet->header[0] = 1; + data_packet->header[1] = 0; + data_packet->header[2] = 0; + compute_header_parity(data_packet); + + data_packet->subpacket[0][0] = 0; + data_packet->subpacket[0][1] = cts >> 16; + data_packet->subpacket[0][2] = cts >> 8; + data_packet->subpacket[0][3] = cts; + data_packet->subpacket[0][4] = n >> 16; + data_packet->subpacket[0][5] = n >> 8; + data_packet->subpacket[0][6] = n; + compute_subpacket_parity(data_packet, 0); + + memcpy(data_packet->subpacket[1], data_packet->subpacket[0], sizeof(data_packet->subpacket[0])); + memcpy(data_packet->subpacket[2], data_packet->subpacket[0], sizeof(data_packet->subpacket[0])); + memcpy(data_packet->subpacket[3], data_packet->subpacket[0], sizeof(data_packet->subpacket[0])); +} + +void set_audio_info_frame(data_packet_t *data_packet, int freq) { + set_null(data_packet); + data_packet->header[0] = 0x84; + data_packet->header[1] = 1; // version + data_packet->header[2] = 10; // len + + const int cc = 1; // 2ch + const int ct = 1; // IEC 60958 PCM + const int ss = 1; // 16bit + const int sf = freq == 48000 ? 3 : (freq == 44100 ? 2 : 0); + const int ca = 0; // FR, FL + const int lsv = 0; // 0db + const int dm_inh = 0; + data_packet->subpacket[0][1] = cc | (ct << 4); + data_packet->subpacket[0][2] = ss | (sf << 2); + data_packet->subpacket[0][4] = ca; + data_packet->subpacket[0][5] = (lsv << 3) | (dm_inh << 7); + + compute_info_frame_checkSum(data_packet); + compute_parity(data_packet); +} + +void set_AVI_info_frame(data_packet_t *data_packet, scan_info s, pixel_format y, colorimetry c, picture_aspect_ratio m, + active_format_aspect_ratio r, RGB_quantization_range q, video_code vic) { + set_null(data_packet); + data_packet->header[0] = 0x82; + data_packet->header[1] = 2; // version + data_packet->header[2] = 13; // len + + int sc = 0; + // int sc = 3; // scaled hv + + data_packet->subpacket[0][1] = (int)(s) | (r == ACTIVE_FORMAT_ASPECT_RATIO_NO_DATA ? 0 : 16) | ((int)(y) << 5); + data_packet->subpacket[0][2] = (int)(r) | ((int)(m) << 4) | ((int)(c) << 6); + data_packet->subpacket[0][3] = sc | ((int)(q) << 2); + data_packet->subpacket[0][4] = (int)(vic); + + compute_info_frame_checkSum(data_packet); + compute_parity(data_packet); +} + +void encode(data_island_stream_t *dst, const data_packet_t *packet, bool vsync, bool hsync) { + int hv = (vsync ? 2 : 0) | (hsync ? 1 : 0); + dst->data[0][0] = makeTERC4x2Char(0b1100 | hv); + dst->data[1][0] = dataGaurdbandSym_; + dst->data[2][0] = dataGaurdbandSym_; + + encode_header(packet, &dst->data[0][1], hv, true); + encode_subpacket(packet, &dst->data[1][1], &dst->data[2][1]); + + dst->data[0][N_DATA_ISLAND_WORDS - 1] = makeTERC4x2Char(0b1100 | hv); + dst->data[1][N_DATA_ISLAND_WORDS - 1] = dataGaurdbandSym_; + dst->data[2][N_DATA_ISLAND_WORDS - 1] = dataGaurdbandSym_; +} \ No newline at end of file diff --git a/software/libdvi/data_packet.h b/software/libdvi/data_packet.h new file mode 100644 index 0000000..df81096 --- /dev/null +++ b/software/libdvi/data_packet.h @@ -0,0 +1,100 @@ +#ifndef DATA_PACKET_H +#define DATA_PACKET_H +#include "pico.h" +#include "audio_ring.h" + +#define TMDS_CHANNELS 3 +#define N_LINE_PER_DATA 2 +#define W_GUARDBAND 2 +#define W_PREAMBLE 8 +#define W_DATA_PACKET 32 + +#ifndef DVI_SYMBOLS_PER_WORD +#define DVI_SYMBOLS_PER_WORD 2 +#endif + +#if DVI_SYMBOLS_PER_WORD != 1 && DVI_SYMBOLS_PER_WORD !=2 +#error "Unsupported value for DVI_SYMBOLS_PER_WORD" +#endif + + +#define W_DATA_ISLAND (W_GUARDBAND * 2 + W_DATA_PACKET) +#define N_DATA_ISLAND_WORDS (W_DATA_ISLAND / DVI_SYMBOLS_PER_WORD) + +typedef enum { + SCAN_INFO_NO_DATA, + OVERSCAN, + UNDERSCAN +} scan_info; + +typedef enum { + RGB, + YCBCR422, + YCBCR444 +} pixel_format; + +typedef enum { + COLORIMETRY_NO_DATA, + ITU601, + ITU709, + EXTENDED +} colorimetry; + +typedef enum { + PIC_ASPECT_RATIO_NO_DATA, + PIC_ASPECT_RATIO_4_3, + PIC_ASPECT_RATIO_16_9 +} picture_aspect_ratio; + +typedef enum { + ACTIVE_FORMAT_ASPECT_RATIO_NO_DATA = -1, + SAME_AS_PAR = 8, + ACTIVE_FORMAT_ASPECT_RATIO_4_3, + ACTIVE_FORMAT_ASPECT_RATIO_16_9, + ACTIVE_FORMAT_ASPECT_RATIO_14_9 +} active_format_aspect_ratio; + +typedef enum { + DEFAULT, + LIMITED, + FULL +} RGB_quantization_range; + +typedef enum { + _640x480P60 = 1, + _720x480P60 = 2, + _1280x720P60 = 4, + _1920x1080I60 = 5, +} video_code; + +typedef struct data_packet { + uint8_t header[4]; + uint8_t subpacket[4][8]; +} data_packet_t; + +typedef struct data_island_stream { + uint32_t data[TMDS_CHANNELS][N_DATA_ISLAND_WORDS]; +} data_island_stream_t; + +// Functions related to the data_packet (requires a data_packet instance) +void compute_header_parity(data_packet_t *data_packet); +void compute_subpacket_parity(data_packet_t *data_packet, int i); +void compute_parity(data_packet_t *data_packet); +void compute_info_frame_checkSum(data_packet_t *data_packet); +void encode_header(const data_packet_t *data_packet, uint32_t *dst, int hv, bool firstPacket); +void encode_subpacket(const data_packet_t *data_packet, uint32_t *dst1, uint32_t *dst2); +void set_null(data_packet_t *data_packet); +int set_audio_sample(data_packet_t *data_packet, const audio_sample_t *p, int n, int frameCt); +void set_audio_clock_regeneration(data_packet_t *data_packet, int CTS, int N); +void set_audio_info_frame(data_packet_t *data_packet, int freq); +void set_AVI_info_frame(data_packet_t *data_packet, scan_info s, pixel_format y, colorimetry c, picture_aspect_ratio m, + active_format_aspect_ratio r, RGB_quantization_range q, video_code vic); + +// Public Functions +extern uint32_t defaultDataPacket12_[N_DATA_ISLAND_WORDS]; +inline uint32_t *getDefaultDataPacket12() { + return defaultDataPacket12_; +} +uint32_t *getDefaultDataPacket0(bool vsync, bool hsync); +void encode(data_island_stream_t *dst, const data_packet_t *packet, bool vsync, bool hsync); +#endif diff --git a/software/libdvi/dvi.c b/software/libdvi/dvi.c index f66377f..512da46 100644 --- a/software/libdvi/dvi.c +++ b/software/libdvi/dvi.c @@ -19,80 +19,106 @@ 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); - dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (void*)SRAM_BASE, &inst->dma_list_active); - 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; + inst->dvi_started = false; + inst->timing_state.v_ctr = 0; + inst->dvi_frame_count = 0; + + dvi_audio_init(inst); + 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[0] = NULL; + inst->tmds_buf_release[1] = 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); + dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (void*)SRAM_BASE, &inst->dma_list_active, false); + dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_error, false); + dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_active_blank, true); + + for (int i = 0; i < DVI_N_TMDS_BUFFERS; ++i) { #if DVI_MONOCHROME_TMDS - tmdsbuf = malloc(inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t)); + void *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)); + void *tmdsbuf = malloc(TMDS_CHANNELS * inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t)); #endif - if (!tmdsbuf) - panic("TMDS buffer allocation failed"); - queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf); - } + if (!tmdsbuf) { + panic("TMDS buffer allocation failed"); + } + queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf); + } + + set_AVI_info_frame(&inst->avi_info_frame, UNDERSCAN, RGB, ITU601, PIC_ASPECT_RATIO_4_3, SAME_AS_PAR, FULL, _640x480P60); + } // 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); + 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); +} + +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[1]) { + queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release[1]); + } + if (inst->tmds_buf_release[0]) { + queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release[0]); + } + inst->tmds_buf_release[1] = NULL; + inst->tmds_buf_release[0] = 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) 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 - ); - } + 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 @@ -100,149 +126,317 @@ 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) { - _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)); + if (inst->dvi_started) { + return; + } + _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); + inst->dvi_started = true; +} + +void dvi_stop(struct dvi_inst *inst) { + if (!inst->dvi_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; - // 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); + dvi_serialiser_enable(&inst->ser_cfg, false); + inst->dvi_started = false; } 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); + 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; + // 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); + 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; + 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(); + while (1) { + 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); + } + __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(); + while (1) { + 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); + } + __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; - } + // 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); + + // 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(); + } + } + + if (inst->tmds_buf_release[1] && !queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release[1])) { + panic("TMDS free queue full in IRQ!"); + } + inst->tmds_buf_release[1] = inst->tmds_buf_release[0]; + inst->tmds_buf_release[0] = NULL; + + 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. + queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf); + --inst->late_scanline_ctr; + } + + switch (inst->timing_state.v_state) { + case DVI_STATE_ACTIVE: + { + bool is_blank_line = false; + if (inst->timing_state.v_ctr < inst->blank_settings.top || + inst->timing_state.v_ctr >= (inst->timing->v_active_lines - inst->blank_settings.bottom)) + { + // Is a Blank Line + is_blank_line = true; + } + 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[0] = 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; + } + } + + if (inst->scanline_is_enabled && (inst->timing_state.v_ctr & 1)) + { + is_blank_line = true; + } + } + + if (is_blank_line) + { + _dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_active_blank); + } + else if (tmdsbuf) + { + dvi_update_scanline_data_dma(inst->timing, tmdsbuf, &inst->dma_list_active, inst->data_island_is_enabled); + _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(inst->timing_state.v_ctr / DVI_VERTICAL_REPEAT); + } + } + break; + + case DVI_STATE_SYNC: + _dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_sync); + if (inst->timing_state.v_ctr == 0) { + ++inst->dvi_frame_count; + } + break; + + default: + _dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_nosync); + break; + } + + if (inst->data_island_is_enabled) { + dvi_update_data_packet(inst); + } } 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); + 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); + 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); +} + +// DVI Data island related +void dvi_audio_init(struct dvi_inst *inst) { + inst->data_island_is_enabled = false; + inst->scanline_is_enabled = false; + inst->audio_freq = 0; + inst->samples_per_frame = 0; + inst->samples_per_line16 = 0; + inst->left_audio_sample_count = 0; + inst->audio_sample_pos = 0; + inst->audio_frame_count = 0; } + +void dvi_enable_data_island(struct dvi_inst *inst) { + inst->data_island_is_enabled = true; + + dvi_setup_scanline_for_vblank_with_audio(inst->timing, inst->dma_cfg, true, &inst->dma_list_vblank_sync); + dvi_setup_scanline_for_vblank_with_audio(inst->timing, inst->dma_cfg, false, &inst->dma_list_vblank_nosync); + dvi_setup_scanline_for_active_with_audio(inst->timing, inst->dma_cfg, (void*)SRAM_BASE, &inst->dma_list_active, false); + dvi_setup_scanline_for_active_with_audio(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_error, false); + dvi_setup_scanline_for_active_with_audio(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_active_blank, true); + + // Setup internal Data Packet streams + dvi_update_data_island_ptr(&inst->dma_list_vblank_sync, &inst->next_data_stream); + dvi_update_data_island_ptr(&inst->dma_list_vblank_nosync, &inst->next_data_stream); + dvi_update_data_island_ptr(&inst->dma_list_active, &inst->next_data_stream); + dvi_update_data_island_ptr(&inst->dma_list_error, &inst->next_data_stream); + dvi_update_data_island_ptr(&inst->dma_list_active_blank, &inst->next_data_stream); +} + +void dvi_update_data_island_ptr(struct dvi_scanline_dma_list *dma_list, data_island_stream_t *stream) { + for (int i = 0; i < N_TMDS_LANES; ++i) { + dma_cb_t *cblist = dvi_lane_from_list(dma_list, i); + uint32_t *src = stream->data[i]; + + if (i == TMDS_SYNC_LANE) { + cblist[1].read_addr = src; + } else { + cblist[2].read_addr = src; + } + } +} + +void dvi_audio_sample_buffer_set(struct dvi_inst *inst, audio_sample_t *buffer, int size) { + audio_ring_set(&inst->audio_ring, buffer, size); +} + +// video_freq: video sampling frequency +// audio_freq: audio sampling frequency +// CTS: Cycle Time Stamp +// N: HDMI Constant +// 128 * audio_freq = video_freq * N / CTS +// e.g.: video_freq = 23495525, audio_freq = 44100 , CTS = 28000, N = 6727 +void dvi_set_audio_freq(struct dvi_inst *inst, int audio_freq, int cts, int n) { + inst->audio_freq = audio_freq; + set_audio_clock_regeneration(&inst->audio_clock_regeneration, cts, n); + set_audio_info_frame(&inst->audio_info_frame, audio_freq); + uint pixelClock = dvi_timing_get_pixel_clock(inst->timing); + uint nPixPerFrame = dvi_timing_get_pixels_per_frame(inst->timing); + uint nPixPerLine = dvi_timing_get_pixels_per_line(inst->timing); + inst->samples_per_frame = (uint64_t)(audio_freq) * nPixPerFrame / pixelClock; + inst->samples_per_line16 = (uint64_t)(audio_freq) * nPixPerLine * 65536 / pixelClock; + dvi_enable_data_island(inst); +} + +void dvi_wait_for_valid_line(struct dvi_inst *inst) { + uint32_t *tmdsbuf = NULL; + queue_peek_blocking_u32(&inst->q_colour_valid, &tmdsbuf); +} + +bool dvi_update_data_packet_(struct dvi_inst *inst, data_packet_t *packet) { + if (inst->samples_per_frame == 0) { + return false; + } + + inst->audio_sample_pos += inst->samples_per_line16; + if (inst->timing_state.v_state == DVI_STATE_FRONT_PORCH) { + if (inst->timing_state.v_ctr == 0) { + if (inst->dvi_frame_count & 1) { + *packet = inst->avi_info_frame; + } else { + *packet = inst->audio_info_frame; + } + inst->left_audio_sample_count = inst->samples_per_frame; + + return true; + } else if (inst->timing_state.v_ctr == 1) { + *packet = inst->audio_clock_regeneration; + + return true; + } + } + int sample_pos_16 = inst->audio_sample_pos >> 16; + int read_size = get_read_size(&inst->audio_ring, false); + int n = MAX(0, MIN(4, MIN(sample_pos_16, read_size))); + inst->audio_sample_pos -= n << 16; + if (n) { + audio_sample_t *audio_sample_ptr = get_read_pointer(&inst->audio_ring); + inst->audio_frame_count = set_audio_sample(packet, audio_sample_ptr, n, inst->audio_frame_count); + increase_read_pointer(&inst->audio_ring, n); + + return true; + } + + return false; +} + +void dvi_update_data_packet(struct dvi_inst *inst) { + data_packet_t packet; + if (!dvi_update_data_packet_(inst, &packet)) { + set_null(&packet); + } + bool vsync = inst->timing_state.v_state == DVI_STATE_SYNC; + encode(&inst->next_data_stream, &packet, inst->timing->v_sync_polarity == vsync, inst->timing->h_sync_polarity); +} \ No newline at end of file diff --git a/software/libdvi/dvi.h b/software/libdvi/dvi.h index 9b05a01..f5a0af7 100644 --- a/software/libdvi/dvi.h +++ b/software/libdvi/dvi.h @@ -1,17 +1,19 @@ #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" +#include "data_packet.h" -typedef void (*dvi_callback_t)(void); +#define TMDS_SYNC_LANE 0 // blue! +#ifndef TMDS_CHANNELS + #define TMDS_CHANNELS 3 +#endif + +typedef void (*dvi_callback_t)(uint); struct dvi_inst { // Config --- @@ -19,6 +21,7 @@ struct dvi_inst { struct dvi_lane_dma_cfg dma_cfg[N_TMDS_LANES]; struct dvi_timing_state timing_state; struct dvi_serialiser_cfg ser_cfg; + dvi_blank_t blank_settings; // Called in the DMA IRQ once per scanline -- careful with the run time! dvi_callback_t scanline_callback; @@ -27,13 +30,14 @@ struct dvi_inst { 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; + struct dvi_scanline_dma_list dma_list_active_blank; // 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; + uint32_t *tmds_buf_release[2]; + // 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; @@ -45,9 +49,32 @@ struct dvi_inst { // Either scanline buffers or frame buffers: queue_t q_colour_valid; queue_t q_colour_free; - + bool dvi_started; + uint dvi_frame_count; + + //Data Packet related + data_packet_t avi_info_frame; + data_packet_t audio_clock_regeneration; + data_packet_t audio_info_frame; + int audio_freq; + int samples_per_frame; + int samples_per_line16; + + bool data_island_is_enabled; + bool scanline_is_enabled; + data_island_stream_t next_data_stream; + audio_ring_t audio_ring; + + int left_audio_sample_count; + int audio_sample_pos; + int audio_frame_count; }; +// Reports DVI status 1: active 0: inactive +inline bool dvi_is_started(struct dvi_inst *inst) { + return inst->dvi_started; +} + // Set up data structures and hardware for DVI. void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue); @@ -55,10 +82,19 @@ 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); + +//Waits for a valid line +void dvi_wait_for_valid_line(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. @@ -69,4 +105,17 @@ void dvi_scanbuf_main_16bpp(struct dvi_inst *inst); void dvi_framebuf_main_8bpp(struct dvi_inst *inst); void dvi_framebuf_main_16bpp(struct dvi_inst *inst); +// Data island (and audio) related api +void dvi_audio_init(struct dvi_inst *inst); +void dvi_enable_data_island(struct dvi_inst *inst); +void dvi_update_data_island_ptr(struct dvi_scanline_dma_list *dma_list, data_island_stream_t *stream); +void dvi_audio_sample_buffer_set(struct dvi_inst *inst, audio_sample_t *buffer, int size); +void dvi_set_audio_freq(struct dvi_inst *inst, int audio_freq, int cts, int n); +void dvi_update_data_packet(struct dvi_inst *inst); +inline void dvi_set_scanline(struct dvi_inst *inst, bool value) { + inst->scanline_is_enabled = value; +} +inline dvi_blank_t *dvi_get_blank_settings(struct dvi_inst *inst) { + return &inst->blank_settings; +} #endif diff --git a/software/libdvi/dvi_config_defs.h b/software/libdvi/dvi_config_defs.h index 448c515..f8c7852 100644 --- a/software/libdvi/dvi_config_defs.h +++ b/software/libdvi/dvi_config_defs.h @@ -60,7 +60,7 @@ // By default we go R, G, B from MSB -> LSB. Override to e.g. swap RGB <-> BGR -// Default 8bpp layout: RGB332, {r[1:0], g[2:0], b[1:0]} +// Default 8bpp layout: RGB332, {r[2:0], g[2:0], b[1:0]} #ifndef DVI_8BPP_RED_MSB #define DVI_8BPP_RED_MSB 7 diff --git a/software/libdvi/dvi_serialiser.c b/software/libdvi/dvi_serialiser.c index 308f23f..6163ea9 100644 --- a/software/libdvi/dvi_serialiser.c +++ b/software/libdvi/dvi_serialiser.c @@ -43,7 +43,9 @@ void dvi_serialiser_init(struct dvi_serialiser_cfg *cfg) { // Use a PWM slice to drive the pixel clock. Both GPIOs must be on the same // slice (lower-numbered GPIO must be even). - assert(cfg->pins_clk % 2 == 0); + //@TODO: Commented to allow debuguing + //assert(cfg->pins_clk % 2 == 0); //Avoid failing if on debug mode + uint slice = pwm_gpio_to_slice_num(cfg->pins_clk); // 5 cycles high, 5 low. Invert one channel so that we get complementary outputs. pwm_config pwm_cfg = pwm_get_default_config(); diff --git a/software/libdvi/dvi_timing.c b/software/libdvi/dvi_timing.c index ec17ff7..4049d72 100644 --- a/software/libdvi/dvi_timing.c +++ b/software/libdvi/dvi_timing.c @@ -214,6 +214,20 @@ static uint32_t __attribute__((aligned(8))) __dvi_const(empty_scanline_tmds)[6] }; #endif +// Black +static uint32_t __dvi_const(black_scanline_tmds)[3] = { + 0x7fd00u, // 0x00, 0x00 + 0x7fd00u, // 0x00, 0x00 + 0x7fd00u, // 0x00, 0x00 +}; + +// Video Gaurdband +static uint32_t __dvi_const(video_gaurdband_syms)[3] = { + 0b10110011001011001100, + 0b01001100110100110011, + 0b10110011001011001100, +}; + void dvi_timing_state_init(struct dvi_timing_state *t) { t->v_ctr = 0; t->v_state = DVI_STATE_FRONT_PORCH; @@ -278,8 +292,40 @@ void dvi_setup_scanline_for_vblank(const struct dvi_timing *t, const struct dvi_ } } +void dvi_setup_scanline_for_vblank_with_audio(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[], + bool vsync_asserted, struct dvi_scanline_dma_list *l) { + + bool vsync = t->v_sync_polarity == vsync_asserted; + const uint32_t *sym_hsync_off = get_ctrl_sym(vsync, !t->h_sync_polarity); + const uint32_t *sym_hsync_on = get_ctrl_sym(vsync, t->h_sync_polarity); + const uint32_t *sym_no_sync = get_ctrl_sym(false, false ); + const uint32_t *sym_preamble_to_data12 = &dvi_ctrl_syms[1]; + const uint32_t *data_packet0 = getDefaultDataPacket0(vsync, t->h_sync_polarity); + + for (int i = 0; i < N_TMDS_LANES; ++i) + { + dma_cb_t *cblist = dvi_lane_from_list(l, i); + if (i == TMDS_SYNC_LANE) + { + _set_data_cb(&cblist[0], &dma_cfg[i], sym_hsync_off, t->h_front_porch / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[1], &dma_cfg[i], data_packet0, N_DATA_ISLAND_WORDS, 0, false); + _set_data_cb(&cblist[2], &dma_cfg[i], sym_hsync_on, (t->h_sync_width - W_DATA_ISLAND) / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[3], &dma_cfg[i], sym_hsync_off, t->h_back_porch / DVI_SYMBOLS_PER_WORD, 2, true); + _set_data_cb(&cblist[4], &dma_cfg[i], sym_hsync_off, t->h_active_pixels / DVI_SYMBOLS_PER_WORD, 2, false); + } + else + { + _set_data_cb(&cblist[0], &dma_cfg[i], sym_no_sync, (t->h_front_porch - W_PREAMBLE) / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[1], &dma_cfg[i], sym_preamble_to_data12, W_PREAMBLE / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[2], &dma_cfg[i], getDefaultDataPacket12(), N_DATA_ISLAND_WORDS, 0, false); + _set_data_cb(&cblist[3], &dma_cfg[i], sym_no_sync, (t->h_sync_width + t->h_back_porch - W_DATA_ISLAND) / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[4], &dma_cfg[i], sym_no_sync, t->h_active_pixels / DVI_SYMBOLS_PER_WORD, 2, false); + } + } +} + void dvi_setup_scanline_for_active(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[], - uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l) { + uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l, bool black) { const uint32_t *sym_hsync_off = get_ctrl_sym(!t->v_sync_polarity, !t->h_sync_polarity); const uint32_t *sym_hsync_on = get_ctrl_sym(!t->v_sync_polarity, t->h_sync_polarity); @@ -304,13 +350,64 @@ void dvi_setup_scanline_for_active(const struct dvi_timing *t, const struct dvi_ } else { // Use read ring to repeat the correct DC-balanced symbol pair on blank scanlines (4 or 8 byte period) - _set_data_cb(&cblist[target_block], &dma_cfg[i], &empty_scanline_tmds[2 * i / DVI_SYMBOLS_PER_WORD], + _set_data_cb(&cblist[target_block], &dma_cfg[i], &(black ? black_scanline_tmds : empty_scanline_tmds)[2 * i / DVI_SYMBOLS_PER_WORD], t->h_active_pixels / DVI_SYMBOLS_PER_WORD, DVI_SYMBOLS_PER_WORD == 2 ? 2 : 3, false); } } } -void __dvi_func(dvi_update_scanline_data_dma)(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l) { +void dvi_setup_scanline_for_active_with_audio(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[], + uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l, bool black) { + + const uint32_t *sym_hsync_off = get_ctrl_sym(!t->v_sync_polarity, !t->h_sync_polarity); + const uint32_t *sym_hsync_on = get_ctrl_sym(!t->v_sync_polarity, t->h_sync_polarity); + const uint32_t *sym_no_sync = get_ctrl_sym(false, false ); + const uint32_t *sym_preamble_to_data12 = &dvi_ctrl_syms[1]; + const uint32_t *sym_preamble_to_video1 = &dvi_ctrl_syms[1]; + const uint32_t *sym_preamble_to_video2 = &dvi_ctrl_syms[0]; + const uint32_t *data_packet0 = getDefaultDataPacket0(!t->v_sync_polarity, t->h_sync_polarity); + + for (int i = 0; i < N_TMDS_LANES; ++i) + { + dma_cb_t *cblist = dvi_lane_from_list(l, i); + + int active_block; + if (i == TMDS_SYNC_LANE) + { + _set_data_cb(&cblist[0], &dma_cfg[i], sym_hsync_off, t->h_front_porch / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[1], &dma_cfg[i], data_packet0, N_DATA_ISLAND_WORDS, 0, false); + _set_data_cb(&cblist[2], &dma_cfg[i], sym_hsync_on, (t->h_sync_width - W_DATA_ISLAND) / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[3], &dma_cfg[i], sym_hsync_off, (t->h_back_porch - W_GUARDBAND) / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[4], &dma_cfg[i], &video_gaurdband_syms[0], W_GUARDBAND / DVI_SYMBOLS_PER_WORD, 2, true); + active_block = 5; + } + else + { + _set_data_cb(&cblist[0], &dma_cfg[i], sym_no_sync, (t->h_front_porch - W_PREAMBLE) / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[1], &dma_cfg[i], sym_preamble_to_data12, W_PREAMBLE / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[2], &dma_cfg[i], getDefaultDataPacket12(), N_DATA_ISLAND_WORDS, 0, false); + _set_data_cb(&cblist[3], &dma_cfg[i], sym_no_sync, (t->h_sync_width + t->h_back_porch - W_DATA_ISLAND - W_PREAMBLE - W_GUARDBAND) / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[4], &dma_cfg[i], i == 1 ? sym_preamble_to_video1 : sym_preamble_to_video2, W_PREAMBLE / DVI_SYMBOLS_PER_WORD, 2, false); + _set_data_cb(&cblist[5], &dma_cfg[i], &video_gaurdband_syms[i], W_GUARDBAND / DVI_SYMBOLS_PER_WORD, 2, false); + active_block = 6; + } + + if (tmdsbuf) + { + // Non-repeating DMA for the freshly-encoded TMDS buffer + _set_data_cb(&cblist[active_block], &dma_cfg[i], tmdsbuf + i * (t->h_active_pixels / DVI_SYMBOLS_PER_WORD), + t->h_active_pixels / DVI_SYMBOLS_PER_WORD, 0, false); + } + else + { + // Use read ring to repeat the correct DC-balanced symbol pair on blank scanlines (4 or 8 byte period) + _set_data_cb(&cblist[active_block], &dma_cfg[i], &(black ? black_scanline_tmds : empty_scanline_tmds)[2 * i / DVI_SYMBOLS_PER_WORD], + t->h_active_pixels / DVI_SYMBOLS_PER_WORD, DVI_SYMBOLS_PER_WORD == 2 ? 2 : 3, false); + } + } +} + +void __dvi_func(dvi_update_scanline_data_dma)(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l, bool audio) { for (int i = 0; i < N_TMDS_LANES; ++i) { #if DVI_MONOCHROME_TMDS const uint32_t *lane_tmdsbuf = tmdsbuf; @@ -318,9 +415,18 @@ void __dvi_func(dvi_update_scanline_data_dma)(const struct dvi_timing *t, const const uint32_t *lane_tmdsbuf = tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD; #endif if (i == TMDS_SYNC_LANE) - dvi_lane_from_list(l, i)[3].read_addr = lane_tmdsbuf; + dvi_lane_from_list(l, i)[audio ? 5 : 3].read_addr = lane_tmdsbuf; else - dvi_lane_from_list(l, i)[1].read_addr = lane_tmdsbuf; + dvi_lane_from_list(l, i)[audio ? 6 : 1].read_addr = lane_tmdsbuf; } } +uint32_t dvi_timing_get_pixels_per_frame(const struct dvi_timing *t) { + uint32_t w = dvi_timing_get_pixels_per_line(t); + uint32_t h = t->v_front_porch + t->v_sync_width + t->v_back_porch + t->v_active_lines; + return w * h; +} + +uint32_t dvi_timing_get_pixels_per_line(const struct dvi_timing *t) { + return t->h_front_porch + t->h_sync_width + t->h_back_porch + t->h_active_pixels; +} \ No newline at end of file diff --git a/software/libdvi/dvi_timing.h b/software/libdvi/dvi_timing.h index bf34937..37ecccd 100644 --- a/software/libdvi/dvi_timing.h +++ b/software/libdvi/dvi_timing.h @@ -30,6 +30,36 @@ enum dvi_line_state { DVI_STATE_COUNT }; +enum dvi_sync_lane_state +{ + DVI_SYNC_LANE_STATE_FRONT_PORCH, + DVI_SYNC_LANE_STATE_SYNC_DATA_ISLAND, // leading guardband, header, trailing guardband + DVI_SYNC_LANE_STATE_SYNC, + DVI_SYNC_LANE_STATE_BACK_PORCH, + DVI_SYNC_LANE_STATE_VIDEO_GUARDBAND, + DVI_SYNC_LANE_STATE_VIDEO, + DVI_SYNC_LANE_STATE_COUNT, +}; + +enum dvi_nosync_lane_state +{ + DVI_NOSYNC_LANE_STATE_CTL0, + DVI_NOSYNC_LANE_STATE_PREAMBLE_TO_DATA, + DVI_NOSYNC_LANE_STATE_DATA_ISLAND, // leading guardband, packet, trailing guardband + DVI_NOSYNC_LANE_STATE_CTL1, + DVI_NOSYNC_LANE_STATE_PREAMBLE_TO_VIDEO, + DVI_NOSYNC_LANE_STATE_VIDEO_GUARDBAND, + DVI_NOSYNC_LANE_STATE_VIDEO, + DVI_NOSYNC_LANE_STATE_COUNT, +}; + +typedef struct dvi_blank { + int left; + int right; + int top; + int bottom; +} dvi_blank_t; + struct dvi_timing_state { uint v_ctr; enum dvi_line_state v_state; @@ -51,10 +81,13 @@ static_assert(__builtin_offsetof(dma_cb_t, c.ctrl) == __builtin_offsetof(dma_cha #define DVI_SYNC_LANE_CHUNKS DVI_STATE_COUNT #define DVI_NOSYNC_LANE_CHUNKS 2 +#define DVI_SYNC_LANE_CHUNKS_WITH_AUDIO DVI_SYNC_LANE_STATE_COUNT +#define DVI_NOSYNC_LANE_CHUNKS_WITH_AUDIO DVI_NOSYNC_LANE_STATE_COUNT + struct dvi_scanline_dma_list { - dma_cb_t l0[DVI_SYNC_LANE_CHUNKS]; - dma_cb_t l1[DVI_NOSYNC_LANE_CHUNKS]; - dma_cb_t l2[DVI_NOSYNC_LANE_CHUNKS]; + dma_cb_t l0[DVI_SYNC_LANE_CHUNKS_WITH_AUDIO]; + dma_cb_t l1[DVI_NOSYNC_LANE_CHUNKS_WITH_AUDIO]; + dma_cb_t l2[DVI_NOSYNC_LANE_CHUNKS_WITH_AUDIO]; }; static inline dma_cb_t* dvi_lane_from_list(struct dvi_scanline_dma_list *l, int i) { @@ -92,8 +125,17 @@ void dvi_setup_scanline_for_vblank(const struct dvi_timing *t, const struct dvi_ bool vsync_asserted, struct dvi_scanline_dma_list *l); void dvi_setup_scanline_for_active(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[], - uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l); + uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l, bool black); + +void dvi_setup_scanline_for_vblank_with_audio(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[], + bool vsync_asserted, struct dvi_scanline_dma_list *l); + +void dvi_setup_scanline_for_active_with_audio(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[], + uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l, bool black); -void dvi_update_scanline_data_dma(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l); +void dvi_update_scanline_data_dma(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l, bool audio); +inline uint32_t dvi_timing_get_pixel_clock(const struct dvi_timing *t) { return t->bit_clk_khz * 100; } +uint32_t dvi_timing_get_pixels_per_frame(const struct dvi_timing *t); +uint32_t dvi_timing_get_pixels_per_line(const struct dvi_timing *t); #endif diff --git a/software/libdvi/tmds_encode.c b/software/libdvi/tmds_encode.c index 472b1a9..e235c21 100644 --- a/software/libdvi/tmds_encode.c +++ b/software/libdvi/tmds_encode.c @@ -86,7 +86,7 @@ void __not_in_flash_func(tmds_encode_data_channel_8bpp)(const uint32_t *pixbuf, int require_lshift = configure_interp_for_addrgen(interp0_hw, channel_msb, channel_lsb, 0, 8, 6, tmds_table); int lshift_upper = configure_interp_for_addrgen(interp1_hw, channel_msb, channel_lsb, 16, 8, 6, tmds_table); assert(!lshift_upper); (void)lshift_upper; - if (require_lshift) + if (require_lshift || (DVI_SYMBOLS_PER_WORD==1)) tmds_encode_loop_8bpp_leftshift(pixbuf, symbuf, n_pix, require_lshift); else tmds_encode_loop_8bpp(pixbuf, symbuf, n_pix); diff --git a/software/libdvi/tmds_table_gen.py b/software/libdvi/tmds_table_gen.py old mode 100755 new mode 100644