/* * This file is part of the Micro Python project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2023 Scott Shawcroft for Adafruit Industries * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "bindings/picodvi/Framebuffer.h" #include "py/gc.h" #include "py/runtime.h" #include "shared-bindings/time/__init__.h" #include "common-hal/pwmio/PWMOut.h" #include "common-hal/rp2pio/StateMachine.h" #include "src/common/pico_stdlib/include/pico/stdlib.h" #include "src/rp2040/hardware_structs/include/hardware/structs/mpu.h" #include "src/rp2_common/cmsis/stub/CMSIS/Device/RaspberryPi/RP2040/Include/RP2040.h" #include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" #include "src/rp2_common/hardware_vreg/include/hardware/vreg.h" #include "src/rp2_common/pico_multicore/include/pico/multicore.h" #include "lib/PicoDVI/software/libdvi/tmds_encode.h" picodvi_framebuffer_obj_t *active_picodvi = NULL; STATIC PIO pio_instances[2] = {pio0, pio1}; static void __not_in_flash_func(core1_main)(void) { // The MPU is reset before this starts. picodvi_framebuffer_obj_t *self = active_picodvi; dvi_register_irqs_this_core(&self->dvi, DMA_IRQ_1); while (queue_is_empty(&self->dvi.q_colour_valid)) { __wfe(); } dvi_start(&self->dvi); // Turn off flash access. After this, it will hard fault. Better than messing // up CIRCUITPY. 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 | // Set execute never and everything else is restricted. MPU_RASR_ENABLE_Msk | (0x1b << MPU_RASR_SIZE_Pos); // Size is 0x10000000 which masks up to SRAM region. MPU->RNR = 7; uint y = 0; while (1) { uint32_t *scanbuf; queue_remove_blocking_u32(&self->dvi.q_colour_valid, &scanbuf); uint32_t *tmdsbuf; queue_remove_blocking_u32(&self->dvi.q_tmds_free, &tmdsbuf); // Check to see if the tmds memory has moved and replace this tmdsbuf // the corresponding on at a new location. size_t old_fb = tmdsbuf[self->tmdsbuf_size - 1]; if (old_fb != (uint32_t)self->framebuffer) { size_t index = ((uint32_t)(tmdsbuf - old_fb)) / self->tmdsbuf_size; // Check our index and hang if it is out of range. Hang is ok since this is core 1. // Better than writing the wrong memory that is shared with CP. while (index >= DVI_N_TMDS_BUFFERS) { } tmdsbuf = self->framebuffer + self->framebuffer_len + (self->tmdsbuf_size * index); tmdsbuf[self->tmdsbuf_size - 1] = (uint32_t)self->framebuffer; } uint pixwidth = self->dvi.timing->h_active_pixels; uint words_per_channel = pixwidth / DVI_SYMBOLS_PER_WORD; if (self->color_depth == 8) { 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); } else if (self->color_depth == 16) { 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); } else if (self->color_depth == 1) { tmds_encode_1bpp(scanbuf, tmdsbuf, pixwidth); } else if (self->color_depth == 2) { tmds_encode_2bpp(scanbuf, tmdsbuf, pixwidth); } queue_add_blocking_u32(&self->dvi.q_tmds_valid, &tmdsbuf); queue_add_blocking_u32(&self->dvi.q_colour_free, &scanbuf); ++y; if (y == self->dvi.timing->v_active_lines) { y = 0; } } __builtin_unreachable(); } static void __not_in_flash_func(core1_scanline_callback)(void) { picodvi_framebuffer_obj_t *self = active_picodvi; uint32_t *next_scanline_buf; next_scanline_buf = self->framebuffer + (self->pitch * self->next_scanline); queue_add_blocking_u32(&self->dvi.q_colour_valid, &next_scanline_buf); // Remove any buffers that were sent back to us. while (queue_try_remove_u32(&self->dvi.q_colour_free, &next_scanline_buf)) { } self->next_scanline += 1; if (self->next_scanline >= self->height) { self->next_scanline = 0; // Update the framebuffer pointer in case it moved. self->framebuffer = self->allocation->ptr; } } extern uint8_t dvi_vertical_repeat; extern bool dvi_monochrome_tmds; void common_hal_picodvi_framebuffer_construct(picodvi_framebuffer_obj_t *self, mp_uint_t width, mp_uint_t height, const mcu_pin_obj_t *clk_dp, const mcu_pin_obj_t *clk_dn, const mcu_pin_obj_t *red_dp, const mcu_pin_obj_t *red_dn, const mcu_pin_obj_t *green_dp, const mcu_pin_obj_t *green_dn, const mcu_pin_obj_t *blue_dp, const mcu_pin_obj_t *blue_dn, mp_uint_t color_depth) { if (active_picodvi != NULL) { mp_raise_msg_varg(&mp_type_RuntimeError, translate("%q in use"), MP_QSTR_picodvi); } bool color_framebuffer = color_depth >= 8; const struct dvi_timing *timing = NULL; if ((width == 640 && height == 480) || (width == 320 && height == 240)) { timing = &dvi_timing_640x480p_60hz; } else if ((width == 800 && height == 480) || (width == 400 && height == 240)) { timing = &dvi_timing_800x480p_60hz; } else { if (height != 480 && height != 240) { mp_raise_ValueError_varg(translate("Invalid %q"), MP_QSTR_height); } mp_raise_ValueError_varg(translate("Invalid %q"), MP_QSTR_width); } // If the width is > 400, then it must not be color frame buffer and vice // versa. if ((width > 400) == color_framebuffer) { mp_raise_ValueError_varg(translate("Invalid %q"), MP_QSTR_color_depth); } bool invert_diffpairs = clk_dn->number < clk_dp->number; int8_t other_pins[4]; int8_t *a; int8_t *b; if (invert_diffpairs) { a = other_pins; b = self->pin_pair; } else { a = self->pin_pair; b = other_pins; } a[0] = clk_dp->number; a[1] = red_dp->number; a[2] = green_dp->number; a[3] = blue_dp->number; b[0] = clk_dn->number; b[1] = red_dn->number; b[2] = green_dn->number; b[3] = blue_dn->number; qstr pin_names[4] = {MP_QSTR_clk_dp, MP_QSTR_red_dp, MP_QSTR_green_dp, MP_QSTR_blue_dp}; for (size_t i = 0; i < 4; i++) { if (other_pins[i] - self->pin_pair[i] != 1) { raise_ValueError_invalid_pin_name(pin_names[i]); } } uint8_t slice = pwm_gpio_to_slice_num(self->pin_pair[0]); pio_program_t program_struct = { .instructions = NULL, .length = 2, .origin = -1 }; size_t pio_index = NUM_PIOS; int free_state_machines[4]; // We may find all four free. We only use the first three. for (size_t i = 0; i < NUM_PIOS; i++) { PIO pio = pio_instances[i]; uint8_t free_count = 0; for (size_t sm = 0; sm < NUM_PIO_STATE_MACHINES; sm++) { if (!pio_sm_is_claimed(pio, sm)) { free_state_machines[free_count] = sm; free_count++; } } if (free_count >= 3 && pio_can_add_program(pio, &program_struct)) { pio_index = i; break; } } if (pio_index == NUM_PIOS) { mp_raise_RuntimeError(translate("All state machines in use")); } self->width = width; self->height = height; size_t tmds_bufs_per_scanline; size_t scanline_width = width; if (color_framebuffer) { dvi_vertical_repeat = 2; dvi_monochrome_tmds = false; tmds_bufs_per_scanline = 3; scanline_width *= 2; } else { dvi_vertical_repeat = 1; dvi_monochrome_tmds = true; // One tmds buffer is used for all three color outputs. tmds_bufs_per_scanline = 1; } self->pitch = (self->width * color_depth) / 8; // Align each row to words. if (self->pitch % sizeof(uint32_t) != 0) { self->pitch += sizeof(uint32_t) - (self->pitch % sizeof(uint32_t)); } self->pitch /= sizeof(uint32_t); size_t framebuffer_size = self->pitch * self->height; self->tmdsbuf_size = tmds_bufs_per_scanline * scanline_width / DVI_SYMBOLS_PER_WORD + 1; size_t total_allocation_size = sizeof(uint32_t) * (framebuffer_size + DVI_N_TMDS_BUFFERS * self->tmdsbuf_size); self->allocation = allocate_memory(total_allocation_size, false, true); if (self->allocation == NULL) { m_malloc_fail(total_allocation_size); return; } // Do the pwmio check last because it claims the pwm slice. if (!pwmio_claim_slice_ab_channels(slice)) { mp_raise_ValueError(translate("All timers for this pin are in use")); } self->pwm_slice = slice; pwmout_never_reset(self->pwm_slice, 0); pwmout_never_reset(self->pwm_slice, 1); for (size_t i = 0; i < 4; i++) { never_reset_pin_number(self->pin_pair[i]); never_reset_pin_number(self->pin_pair[i] + 1); } for (size_t i = 0; i < 3; i++) { rp2pio_statemachine_never_reset(pio_instances[pio_index], free_state_machines[i]); } // For the output. user_irq_claim(DMA_IRQ_1); self->framebuffer_len = framebuffer_size; self->framebuffer = self->allocation->ptr; self->color_depth = color_depth; self->dvi.timing = timing; self->dvi.ser_cfg.pio = pio_instances[pio_index]; self->dvi.ser_cfg.sm_tmds[0] = free_state_machines[0]; self->dvi.ser_cfg.sm_tmds[1] = free_state_machines[1]; self->dvi.ser_cfg.sm_tmds[2] = free_state_machines[2]; self->dvi.ser_cfg.pins_clk = self->pin_pair[0]; self->dvi.ser_cfg.pins_tmds[0] = self->pin_pair[1]; self->dvi.ser_cfg.pins_tmds[1] = self->pin_pair[2]; self->dvi.ser_cfg.pins_tmds[2] = self->pin_pair[3]; self->dvi.ser_cfg.invert_diffpairs = invert_diffpairs; self->dvi.scanline_callback = core1_scanline_callback; vreg_set_voltage(VREG_VOLTAGE_1_20); common_hal_time_delay_ms(10); set_sys_clock_khz(timing->bit_clk_khz, true); // Run at TMDS bit clock self->tmds_lock = next_striped_spin_lock_num(); self->colour_lock = next_striped_spin_lock_num(); dvi_init(&self->dvi, self->tmds_lock, self->colour_lock); // Load up the TMDS buffers. for (int i = 0; i < DVI_N_TMDS_BUFFERS; ++i) { uint32_t *tmdsbuf = self->framebuffer + (self->framebuffer_len + self->tmdsbuf_size * i); // Use the last word in the buffer to track its original root. That way // we can detect when framebuffer is moved. tmdsbuf[self->tmdsbuf_size - 1] = (uint32_t)self->framebuffer; queue_add_blocking_u32(&self->dvi.q_tmds_free, &tmdsbuf); } active_picodvi = self; // Core 1 will wait until it sees the first colour buffer, then start up the // DVI signalling. multicore_launch_core1(core1_main); self->next_scanline = 0; uint32_t *next_scanline_buf = self->framebuffer + (self->pitch * self->next_scanline); queue_add_blocking_u32(&self->dvi.q_colour_valid, &next_scanline_buf); self->next_scanline += 1; next_scanline_buf = self->framebuffer + (self->pitch * self->next_scanline); queue_add_blocking_u32(&self->dvi.q_colour_valid, &next_scanline_buf); self->next_scanline += 1; // Wait for the second core to run dvi_start because it is in flash. Once it is done, // it'll pull from this queue. Not waiting may lead to us reading flash when this core // doesn't want us to. while (queue_get_level(&self->dvi.q_colour_valid) == 2) { } } STATIC void _turn_off_dma(uint8_t channel) { dma_channel_config c = dma_channel_get_default_config(channel); channel_config_set_enable(&c, false); dma_channel_set_config(channel, &c, false /* trigger */); if (dma_channel_is_busy(channel)) { dma_channel_abort(channel); } dma_channel_set_irq1_enabled(channel, false); dma_channel_unclaim(channel); } void common_hal_picodvi_framebuffer_deinit(picodvi_framebuffer_obj_t *self) { if (common_hal_picodvi_framebuffer_deinited(self)) { return; } // Stop the other core and free resources. // Grab the locks before shutting down the other core so we don't leave the // locks locked. spin_lock_t *tmds_lock = spin_lock_instance(self->tmds_lock); spin_lock_t *colour_lock = spin_lock_instance(self->colour_lock); uint32_t tmds_save = spin_lock_blocking(tmds_lock); uint32_t colour_save = spin_lock_blocking(colour_lock); multicore_reset_core1(); spin_unlock(colour_lock, colour_save); spin_unlock(tmds_lock, tmds_save); for (size_t i = 0; i < 4; i++) { reset_pin_number(self->pin_pair[i]); reset_pin_number(self->pin_pair[i] + 1); } for (int i = 0; i < N_TMDS_LANES; ++i) { // Turn off data first because it chains to the ctrl DMA. _turn_off_dma(self->dvi.dma_cfg[i].chan_data); _turn_off_dma(self->dvi.dma_cfg[i].chan_ctrl); } pwm_set_enabled(self->pwm_slice, false); pwmout_free(self->pwm_slice, 0); pwmout_free(self->pwm_slice, 1); pio_program_t program_struct = { .length = 2 }; PIO pio = self->dvi.ser_cfg.pio; for (size_t i = 0; i < 3; i++) { int sm = self->dvi.ser_cfg.sm_tmds[i]; pio_sm_set_enabled(pio, sm, false); pio_sm_unclaim(pio, sm); rp2pio_statemachine_reset_ok(pio, sm); } pio_remove_program(pio, &program_struct, self->dvi.ser_cfg.prog_offs); if (user_irq_is_claimed(DMA_IRQ_1)) { user_irq_unclaim(DMA_IRQ_1); } active_picodvi = NULL; free_memory(self->allocation); self->framebuffer = NULL; self->base.type = &mp_type_NoneType; } bool common_hal_picodvi_framebuffer_deinited(picodvi_framebuffer_obj_t *self) { return self->framebuffer == NULL; } void common_hal_picodvi_framebuffer_refresh(picodvi_framebuffer_obj_t *self) { } int common_hal_picodvi_framebuffer_get_width(picodvi_framebuffer_obj_t *self) { return self->width; } int common_hal_picodvi_framebuffer_get_height(picodvi_framebuffer_obj_t *self) { return self->height; } int common_hal_picodvi_framebuffer_get_color_depth(picodvi_framebuffer_obj_t *self) { return self->color_depth; } mp_int_t common_hal_picodvi_framebuffer_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { picodvi_framebuffer_obj_t *self = (picodvi_framebuffer_obj_t *)self_in; bufinfo->buf = self->framebuffer; char typecode = 'B'; if (self->color_depth == 16) { typecode = 'H'; } bufinfo->typecode = typecode; bufinfo->len = self->framebuffer_len * sizeof(uint32_t); return 0; } int common_hal_picodvi_framebuffer_get_row_stride(picodvi_framebuffer_obj_t *self) { // Pitch is in words but row stride is expected as bytes. return self->pitch * sizeof(uint32_t); }