#include #include #include #include #include #include #include #include "cache.h" #include "defs.h" #include "info.h" #include "logging.h" #include "rpi-aux.h" #include "rpi-gpio.h" #include "rpi-interrupts.h" #include "rpi-mailbox-interface.h" #include "startup.h" #include "rpi-mailbox.h" #include "osd.h" #include "cpld.h" #include "cpld_atom.h" #include "cpld_rgb.h" #include "cpld_yuv.h" #include "cpld_simple.h" #include "cpld_null.h" #include "geometry.h" #include "filesystem.h" #include "rgb_to_fb.h" #include "jtag/update_cpld.h" #include "vid_cga_comp.h" #include "videocore.c" #include "gitversion.h" #include "vid_cga_comp.h" // #define INSTRUMENT_CAL #define NUM_CAL_PASSES 1 typedef void (*func_ptr)(); // ============================================================= // Define the PLL to be used for the sampling clock // ============================================================= // // Choose between PLLA, PLLC and PLLD // // PLLA is the auxiliary PLL, used to drive the CCP2 (Compact Camera Port 2) transmitter clock. // PLLB is the CPU clock // PLLC is the core PLL, used to drive the core VPU clock and the UART // PLLD is the display PLL, used to drive DSI display panels. // // Power-on defaults are values for the Pi Zero // SYS_CLK_DIVIDER is the ratio between the UART source clock and the Core0 output of PLLC // This is typically 3 or 4, depending on the Pi Model. // We need to know this to correct serial speed when PLLC used. // it should really be read from a register as it changes with core freq (register address not known at this time) // however it is only needed for PLLC and all models now use PLLA #if defined(RPI4) #define USE_PLLD4 //can only use PLLD on Pi 4 due to some hard coded workarounds but it is the only practical one anyway #else #define USE_PLLA #endif //PLL defaults for different Pi versions //pi0 = 2400/2000/2400/2000 //pi1 = 2000/1400/2000/2000 //pi2 = 2000/1800/2000/2000 unconfirmed //pi3 = 2400/2400/2400/2000 //pi4 = 3000/3000/3000/3000 #ifdef USE_PLLA #define PLL_NAME "PLLA" // power-on default = off #define GPCLK_SOURCE 4 // PLLA_PER (4) used as source #define DEFAULT_GPCLK_DIVISOR 6 // 2400MHz / 4 / 6 = 100MHz #define PLL_CTRL PLLA_CTRL #define PLL_FRAC PLLA_FRAC #define ANA1 PLLA_ANA1 #define PER PLLA_PER #define PLLA_PER_VALUE 4 #define MIN_PLL_FREQ 1200000000 #define MAX_PLL_FREQ 2400000000 #endif #ifdef USE_PLLC #define PLL_NAME "PLLC" // power-on default = 1200MHz #define GPCLK_SOURCE 5 // PLLC_PER (2) used as source #define DEFAULT_GPCLK_DIVISOR 12 // 2400MHz / 2 / 12 = 100MHz #define PLL_CTRL PLLC_CTRL #define PLL_FRAC PLLC_FRAC #define ANA1 PLLC_ANA1 #define PER PLLC_PER #define MIN_PLL_FREQ 1200000000 #define MAX_PLL_FREQ 2400000000 #endif #ifdef USE_PLLD #define PLL_NAME "PLLD" // power-on default = 500MHz #define GPCLK_SOURCE 6 // PLLD_PER (4) used as source #define DEFAULT_GPCLK_DIVISOR 6 // 2400MHz / 4 / 6 = 100MHz #define PLL_CTRL PLLD_CTRL #define PLL_FRAC PLLD_FRAC #define ANA1 PLLD_ANA1 #define PER PLLD_PER #define MIN_PLL_FREQ 1200000000 #define MAX_PLL_FREQ 2400000000 #endif #ifdef USE_PLLA4 #define PLL_NAME "PLLA" // power-on default = 3000MHz #define GPCLK_SOURCE 4 // PLLA_PER (5) used as source #define DEFAULT_GPCLK_DIVISOR 6 // 3000MHz / 5 / 6 #define PLL_CTRL PLLA_CTRL #define PLL_FRAC PLLA_FRAC #define ANA1 PLLA_ANA1 #define PER PLLA_PER #define PLLA_PER_VALUE 4 #define MIN_PLL_FREQ 2900000000 #define MAX_PLL_FREQ 3050000000 #define MAX_PLL_EXTENSION 0 #endif #ifdef USE_PLLC4 #define PLL_NAME "PLLC" // power-on default = 2592MHz #define GPCLK_SOURCE 5 // PLLC_PER (5) used as source #define DEFAULT_GPCLK_DIVISOR 8 // 2592MHz / 5 / 8 #define PLL_CTRL PLLC_CTRL #define PLL_FRAC PLLC_FRAC #define ANA1 PLLC_ANA1 #define PER PLLC_PER #define PLLC_PER_VALUE 4 // default is 4 but increase to 5 so any other peripherals don't get overclocked #define MIN_PLL_FREQ 2592000000 // PAL/NTSC mode, needs a 108mhz clock, so one of the PLL's needs to run at an integer multiple of 108mhz (from pi forum) #define MAX_PLL_FREQ 3000000000 #define MAX_PLL_EXTENSION 500000000 #endif #ifdef USE_PLLD4 #define PLL_NAME "PLLD" // power-on default = 3000MHz #define GPCLK_SOURCE 6 // PLLD_PER (4) used as source #define DEFAULT_GPCLK_DIVISOR 8 // 3000MHz / 4 / 8 #define PLL_CTRL PLLD_CTRL #define PLL_FRAC PLLD_FRAC #define ANA1 PLLD_ANA1 #define PER PLLD_PER #define PLLD_PER_VALUE 4 // default is 4 but increase to 5 so any other peripherals don't get overclocked #define PLLD_CORE_VALUE 5 // default is 5 but decrease to 4 so the core doesn't get underclocked #define MIN_PLL_FREQ 2500000000 #define MAX_PLL_FREQ 3000000000 #define MAX_PLL_EXTENSION 500000000 #endif // ============================================================= // Forward declarations // ============================================================= static void cpld_init(); // ============================================================= // Global variables // ============================================================= cpld_t *cpld = NULL; int clock_error_ppm = 0; int vsync_time_ns = 0; int calculated_vsync_time_ns = 0; capture_info_t *capinfo; clk_info_t clkinfo; // ============================================================= // Local variables // ============================================================= static capture_info_t set_capinfo __attribute__((aligned(32))); static uint32_t cpld_version_id; static int modeset; static int interlaced; static int clear; static volatile int delay; static double pllh_clock = 0; static int genlocked = 0; static int resync_count = 0; static int target_difference = 0; static int half_frame_rate = 0; static int source_vsync_freq_hz = 0; static int info_display_vsync_freq_hz = 0; static double source_vsync_freq = 0; static double display_vsync_freq = 0; static double info_display_vsync_freq = 0; static char status[256]; static int restart_profile = 0; static int last_divider = -1; // ============================================================= // OSD parameters // ============================================================= static int old_refresh = -1; static int old_resolution = -1; static int old_hdmi_auto = -1; static int old_hdmi_mode = -1; //static int x_resolution = 0; //static int y_resolution = 0; static char resolution_name[MAX_NAMES_WIDTH]; static char auto_workaround_path[MAX_NAMES_WIDTH] = ""; static int gscaling = GSCALING_INTEGER; static int filtering = DEFAULT_FILTERING; static int old_filtering = - 1; static int lines_per_2_vsyncs = 0; static int lines_per_vsync = 0; static int vsync_width_lines = 5; static int one_line_time_ns = 0; static int nlines_time_ns = 0; static int nlines_ref_ns = 0; static int nominal_cpld_clock = 0; static int one_vsync_time_ns = 0; static int adjusted_clock; static int reboot_required = 0; static int resolution_warning = 0; static int vlock_limited = 0; static int current_display_buffer = 0; static int h_overscan = 0; static int v_overscan = 0; static int adj_h_overscan = 0; static int adj_v_overscan = 0; static int config_overscan_left = 0; static int config_overscan_right = 0; static int config_overscan_top = 0; static int config_overscan_bottom = 0; static int startup_overscan = 0; static int cpuspeed = 1000; static int cpld_fail_state = CPLD_NORMAL; static int helper_flag = 0; static int simple_detected = 0; static int mono_detected = 0; static int supports8bit = 0; static int newanalog = 0; static int force_genlock_range = 0; static int DAC_detected = 0; static unsigned int pll_freq = 0; static unsigned int new_clock = 0; static unsigned int old_pll_freq = 0; static unsigned int old_clock = 0; static unsigned int prediv = 0; static unsigned int pll_scale = 0; static unsigned int min_pll_freq = 0; static unsigned int max_pll_freq = 0; static unsigned int gpclk_divisor = 0; static int ppm_range = 1; static int ppm_range_count = 0; static int powerup = 1; static int hsync_threshold_switch = 0; static int display_list_offset = 5; static int restricted_slew_rate = 0; static unsigned int framebuffer = 0; static unsigned int framebuffer_topbits = 0; static int hdmi_error_ppm = 0; static volatile uint32_t display_list_index = 0; volatile uint32_t* display_list; volatile uint32_t* pi4_hdmi0_regs; static int parameters[MAX_PARAMETERS] = {0}; #ifndef USE_ARM_CAPTURE void start_vc() { int func; func = (int) &___videocore_asm[0]; RPI_PropertyInit(); RPI_PropertyAddTag(TAG_LAUNCH_VPU1,func,0,0,0,0,0,0); RPI_PropertyProcessNoCheck(); } #endif void start_vc_bench(int type) { int func; func = (int) &___videocore_asm[0]; RPI_PropertyInit(); RPI_PropertyAddTag(TAG_EXECUTE_CODE,func,type,0,0,0,0,0); RPI_PropertyProcess(); } static int current_genlock_mode = -1; static const char *sync_names[] = { "-H-V", "+H-V", "-H+V", "+H+V", "Comp", "InvComp" }; static const char *sync_names_long[] = { "Separate -H -V", "Separate +H -V", "Separate -H +V", "Separate +H +V", "Composite", "Inverted Composite" }; static const char *mixed_names[] = { "Separate H & V CPLD", "Mixed H & V CPLD" }; // Calculated so that the constants from librpitx work static volatile uint32_t *gpioreg; // Temporary buffer that must be at least as large as a frame buffer static unsigned char last[4096 * 1024] __attribute__((aligned(32))); #ifndef USE_PROPERTY_INTERFACE_FOR_FB typedef struct { uint32_t width; uint32_t height; uint32_t virtual_width; uint32_t virtual_height; volatile uint32_t pitch; volatile uint32_t depth; uint32_t x_offset; uint32_t y_offset; volatile uint32_t pointer; volatile uint32_t size; } framebuf; // The + 0x10000 is to miss the property buffer static framebuf *fbp = (framebuf *) (UNCACHED_MEM_BASE + 0x10000); #endif // ============================================================= // Private methods // ============================================================= void delay_in_arm_cycles_cpu_adjust(int cycles) { delay_in_arm_cycles((int) ((double)cycles * (double)cpuspeed / 1000)); } void reboot() { *PM_WDOG = PM_PASSWORD | 1; *PM_RSTC = PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET; while(1); } // 0 0 Hz Ground // 1 19.2 MHz oscillator // 2 0 Hz testdebug0 // 3 0 Hz testdebug1 // 4 0 Hz PLLA // 5 1000 MHz PLLC (changes with overclock settings) // 6 500 MHz PLLD // 7 216 MHz HDMI auxiliary // 8-15 0 Hz Ground // Source 1 = OSC = 19.2MHz // Source 4 = PLLA = 0MHz // Source 5 = PLLC = core_freq * 3 // Source 6 = PLLD = 500MHz static void init_gpclk(int source, int divisor) { log_debug("A GP_CLK1_DIV = %08"PRIx32, *GP_CLK1_DIV); log_debug("B GP_CLK1_CTL = %08"PRIx32, *GP_CLK1_CTL); // Stop the clock generator (retaining the existing source) *GP_CLK1_CTL = CM_PASSWORD | ((*GP_CLK1_CTL) & ~GZ_CLK_ENA); // Wait for BUSY low log_debug("C GP_CLK1_CTL = %08"PRIx32, *GP_CLK1_CTL); while ((*GP_CLK1_CTL) & GZ_CLK_BUSY) {} log_debug("D GP_CLK1_CTL = %08"PRIx32, *GP_CLK1_CTL); // Configure the clock generator *GP_CLK1_CTL = CM_PASSWORD | source; *GP_CLK1_DIV = CM_PASSWORD | (divisor << 12); log_debug("E GP_CLK1_CTL = %08"PRIx32, *GP_CLK1_CTL); // Start the clock generator *GP_CLK1_CTL = CM_PASSWORD | (source | GZ_CLK_ENA); log_debug("F GP_CLK1_CTL = %08"PRIx32, *GP_CLK1_CTL); while (!((*GP_CLK1_CTL) & GZ_CLK_BUSY)) {} // Wait for BUSY high log_debug("G GP_CLK1_CTL = %08"PRIx32, *GP_CLK1_CTL); log_debug("H GP_CLK1_DIV = %08"PRIx32, *GP_CLK1_DIV); } #ifdef USE_PROPERTY_INTERFACE_FOR_FB // this is the current one used static void init_framebuffer(capture_info_t *capinfo) { static int last_width = -1; static int last_height = -1; int width = 0; int height = 0; rpi_mailbox_property_t *mp; if (capinfo->width != last_width || capinfo->height != last_height) { //if (last_width != -1 && last_height != -1) { // clear_full_screen(); //} // Fill in the frame buffer structure with a small dummy frame buffer first /* Initialise a framebuffer... */ RPI_PropertyInit(); RPI_PropertyAddTag(TAG_ALLOCATE_BUFFER, 0x02000000); RPI_PropertyAddTag(TAG_SET_PHYSICAL_SIZE, 64, 64); #ifdef MULTI_BUFFER RPI_PropertyAddTag(TAG_SET_VIRTUAL_SIZE, 64, 64); #else RPI_PropertyAddTag(TAG_SET_VIRTUAL_SIZE, 64, 64); #endif RPI_PropertyAddTag(TAG_SET_DEPTH, capinfo->bpp); RPI_PropertyProcess(); // FIXME: A small delay (like the log) is neccessary here // or the RPI_PropertyGet seems to return garbage log_info("Width or Height differ from last FB: Setting dummy 64x64 framebuffer"); } //last_width = capinfo->width; //last_height = capinfo->height; /* work out if overscan needed */ int h_size = get_hdisplay() - config_overscan_left - config_overscan_right; int v_size = get_vdisplay() - config_overscan_top - config_overscan_bottom; h_overscan = 0; v_overscan = 0; adj_h_overscan = 0; adj_v_overscan = 0; int adjusted_width = capinfo->width; int adjusted_height = capinfo->height; if (get_gscaling() == GSCALING_INTEGER) { if (!((capinfo->mode7 && parameters[F_MODE7_SCALING] == SCALING_UNEVEN) ||(!capinfo->mode7 && parameters[F_NORMAL_SCALING] == SCALING_UNEVEN))) { int width = adjusted_width >> ((capinfo->sizex2 & SIZEX2_DOUBLE_WIDTH) >> 1); int hscale = h_size / width; h_overscan = h_size - (hscale * width); adj_h_overscan = h_overscan; if ((hscale & 1) != 0 && hscale > 1) { // add 1 when scale is odd number to work around pixel column duplication scaler rounding error adj_h_overscan++; } } int height = capinfo->height >> (capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT); if (geometry_get_value(VIDEO_TYPE) == VIDEO_LINE_DOUBLED) { height >>= 1; } int vscale = v_size / height; v_overscan = v_size - (vscale * height); adj_v_overscan = v_overscan; //if ((vscale & 1) != 0 && vscale > 1) { // add 1 when scale is odd number // adj_v_overscan++; //} } int left_overscan = adj_h_overscan >> 1; int right_overscan = left_overscan + (adj_h_overscan & 1); int top_overscan = adj_v_overscan >> 1; int bottom_overscan = top_overscan + (adj_v_overscan & 1); left_overscan += config_overscan_left; right_overscan += config_overscan_right; top_overscan += config_overscan_top; bottom_overscan += config_overscan_bottom; log_info("Overscan L=%d, R=%d, T=%d, B=%d",left_overscan, right_overscan, top_overscan, bottom_overscan); /* Initialise a framebuffer... */ RPI_PropertyInit(); RPI_PropertyAddTag(TAG_ALLOCATE_BUFFER, 0x02000000); RPI_PropertyAddTag(TAG_SET_PHYSICAL_SIZE, adjusted_width, adjusted_height); #ifdef MULTI_BUFFER RPI_PropertyAddTag(TAG_SET_VIRTUAL_SIZE, adjusted_width, adjusted_height * NBUFFERS); #else RPI_PropertyAddTag(TAG_SET_VIRTUAL_SIZE, adjusted_width, adjusted_height); #endif RPI_PropertyAddTag(TAG_SET_DEPTH, capinfo->bpp); RPI_PropertyAddTag(TAG_SET_OVERSCAN, top_overscan, bottom_overscan, left_overscan, right_overscan); RPI_PropertyAddTag(TAG_GET_PITCH); RPI_PropertyAddTag(TAG_GET_PHYSICAL_SIZE); RPI_PropertyAddTag(TAG_GET_DEPTH); RPI_PropertyProcess(); // FIXME: A small delay (like the log) is neccessary here // or the RPI_PropertyGet seems to return garbage delay_in_arm_cycles_cpu_adjust(4000000); log_info("Initialised Framebuffer"); if ((mp = RPI_PropertyGet(TAG_GET_PHYSICAL_SIZE))) { width = mp->data.buffer_32[0]; height = mp->data.buffer_32[1]; if (width != adjusted_width || height != adjusted_height) { log_info("Invalid frame buffer dimensions %d/%d, %d/%d - maybe HDMI not connected - rebooting", width, adjusted_width, height, adjusted_height); delay_in_arm_cycles_cpu_adjust(1000000000); reboot(); } } if ((mp = RPI_PropertyGet(TAG_GET_PITCH))) { capinfo->pitch = mp->data.buffer_32[0]; //log_info("Pitch: %d bytes", capinfo->pitch); } if ((mp = RPI_PropertyGet(TAG_ALLOCATE_BUFFER))) { framebuffer = (unsigned int)mp->data.buffer_32[0]; // On the Pi 2/3 the mailbox returns the address with bits 31..30 set, which is wrong capinfo->fb = (unsigned char *)(framebuffer & 0x3fffffff); } //Initialize the palette osd_update_palette(); /* volatile uint32_t d; volatile uint32_t dlist_start; dlist_start = *SCALER_DISPLIST1; log_info("SCALER_DISPLIST1 = %08X", dlist_start); int i = dlist_start; do { d = display_list[i++]; log_info("%08X", d); } while (d != 0x80000000); */ // modify display list if 16bpp to switch from RGB 565 to ARGB 4444 if (capinfo->bpp == 16) { //have to wait for field sync for display list to be updated wait_for_pi_fieldsync(); wait_for_pi_fieldsync(); unsigned int dli; //read the index pointer into the display list RAM display_list_index = (uint32_t) *SCALER_DISPLIST1; display_list_offset = 0; for(int i = 6; i > 3; i--) { do { dli = display_list[display_list_index + i]; } while (dli == 0xff000000); if ((dli & 0x3fffffff) == (framebuffer & 0x3fffffff)) { //start of frame buffer moves in position depending on scaling and resolution so search for it display_list_offset = i; } } if (display_list_offset == 0) { #ifdef RPI4 display_list_offset = 6; //default #else display_list_offset = 5; //default #endif } do { framebuffer_topbits = display_list[display_list_index + display_list_offset]; } while (framebuffer_topbits == 0xff000000); framebuffer_topbits &= 0xc0000000; do { dli = display_list[display_list_index]; } while (dli == 0xFF000000); display_list[display_list_index] = (dli & ~0x600f) | (PIXEL_ORDER << 13) | PIXEL_FORMAT; //log_info("Modified display list word at %08X = %08X", display_list_index, display_list[display_list_index]); log_info("Size: %dx%d (req %dx%d). Addr: %8.8X (%8.8X) Offset = %d, %1X", width, height, capinfo->width, capinfo->height, (unsigned int)capinfo->fb, framebuffer, display_list_offset, framebuffer_topbits >> 28); } else { framebuffer_topbits = 0; display_list_offset = 0; log_info("Size: %dx%d (req %dx%d). Addr: %8.8X (%8.8X)", width, height, capinfo->width, capinfo->height, (unsigned int)capinfo->fb, framebuffer); } } #else // An alternative way to initialize the framebuffer using mailbox channel 1 // // I was hoping it would then be possible to page flip just by modifying the structure // in-place. Unfortunately that didn't work, but the code might be useful in the future. // THIS CALL IS NOT USED AND HAS NOT BEEN UPDATED static void init_framebuffer(capture_info_t *capinfo) { static int last_width = -1; static int last_height = -1; log_debug("Framebuf struct address: %p", fbp); if (capinfo->width != last_width || capinfo->height != last_height) { log_info("Width or Height differ from last FB: Setting dummy 64x64 framebuffer"); // Fill in the frame buffer structure with a small dummy frame buffer first fbp->width = 64; fbp->height = 64; fbp->virtual_width = 64; #ifdef MULTI_BUFFER fbp->virtual_height = 64; #else fbp->virtual_height = 64; #endif fbp->pitch = 0; fbp->depth = capinfo->bpp; fbp->x_offset = 0; fbp->y_offset = 0; fbp->pointer = 0; fbp->size = 0; // Send framebuffer struct to the mailbox // // The +0x40000000 ensures the GPU bypasses it's cache when accessing // the framebuffer struct. If this is not done, the screen still initializes // but the ARM doesn't see the updated value for a very long time // i.e. the commented out section of code below is needed, and this eventually // exits with i=4603039 // // 0xC0000000 should be added if disable_l2cache=1 RPI_Mailbox0Write(MB0_FRAMEBUFFER, ((unsigned int)fbp) + 0xC0000000); // Wait for the response (0) RPI_Mailbox0Read(MB0_FRAMEBUFFER); } last_width = capinfo->width; last_height = capinfo->height; // Fill in the frame buffer structure fbp->width = capinfo->width; fbp->height = capinfo->height; fbp->virtual_width = capinfo->width; #ifdef MULTI_BUFFER fbp->virtual_height = capinfo->height * NBUFFERS; #else fbp->virtual_height = capinfo->height; #endif fbp->pitch = 0; fbp->depth = capinfo->bpp; fbp->x_offset = 0; fbp->y_offset = 0; fbp->pointer = 0; fbp->size = 0; // Send framebuffer struct to the mailbox // // The +0x40000000 ensures the GPU bypasses it's cache when accessing // the framebuffer struct. If this is not done, the screen still initializes // but the ARM doesn't see the updated value for a very long time // i.e. the commented out section of code below is needed, and this eventually // exits with i=4603039 // // 0xC0000000 should be added if disable_l2cache=1 RPI_Mailbox0Write(MB0_FRAMEBUFFER, ((unsigned int)fbp) + 0xC0000000); // Wait for the response (0) RPI_Mailbox0Read(MB0_FRAMEBUFFER); capinfo->pitch = fbp->pitch; capinfo->fb = (unsigned char*)(fbp->pointer); int width = fbp->width; int height = fbp->height; // See comment above // int i = 0; // while (!pitch || !fb) { // pitch = fbp->pitch; // fb = (unsigned char*)(fbp->pointer); // i++; // } // log_info("i=%d", i); log_info("Initialised Framebuffer: %dx%d ", width, height); log_info("Pitch: %d bytes", capinfo->pitch); log_debug("Framebuffer address: %8.8X", (unsigned int)capinfo->fb); // On the Pi 2/3 the mailbox returns the address with bits 31..30 set, which is wrong capinfo->fb = (unsigned char *)(((unsigned int) capinfo->fb) & 0x3fffffff); // Initialize the palette osd_update_palette(); } #endif //info about using ANA1 prediv extracted from: //https://github.com/torvalds/linux/blob/43570f0383d6d5879ae585e6c3cf027ba321546f/drivers/clk/bcm/clk-bcm2835.c void log_plla() { int ANA1_PREDIV = 0; #ifndef RPI4 //prediv not available on BCM2711 see: https://elixir.free-electrons.com/linux/v5.7.19/source/drivers/clk/bcm/clk-bcm2835.c#L529 ANA1_PREDIV = (gpioreg[PLLA_ANA1] >> 14) & 1; #endif int NDIV = (gpioreg[PLLA_CTRL] & 0x3ff) << ANA1_PREDIV; int FRAC = gpioreg[PLLA_FRAC] << ANA1_PREDIV; double clock = CRYSTAL * ((double)NDIV + ((double)FRAC) / ((double)(1 << 20))); log_info("PLLA: %lf ANA1 = %08x", clock, ANA1_PREDIV );//gpioreg[PLLA_ANA1]); log_info("PLLA: PDIV=%d NDIV=%d CTRL=%08x FRAC=%d DSI0=%d CORE=%d PER=%d CCP2=%d", (gpioreg[PLLA_CTRL] >> 12) & 0x7, gpioreg[PLLA_CTRL] & 0x3ff, gpioreg[PLLA_CTRL], gpioreg[PLLA_FRAC], gpioreg[PLLA_DSI0], gpioreg[PLLA_CORE], gpioreg[PLLA_PER], gpioreg[PLLA_CCP2]); } void log_pllb() { int ANA1_PREDIV = 0; #ifndef RPI4 //prediv not available on BCM2711 see: https://elixir.free-electrons.com/linux/v5.7.19/source/drivers/clk/bcm/clk-bcm2835.c#L529 ANA1_PREDIV = (gpioreg[PLLB_ANA1] >> 14) & 1; #endif int NDIV = (gpioreg[PLLB_CTRL] & 0x3ff) << ANA1_PREDIV; int FRAC = gpioreg[PLLB_FRAC] << ANA1_PREDIV; double clock = CRYSTAL * ((double)NDIV + ((double)FRAC) / ((double)(1 << 20))); log_info("PLLB: %lf ANA1 = %08x", clock, gpioreg[PLLB_ANA1]); log_info("PLLB: PDIV=%d NDIV=%d CTRL=%08x FRAC=%d ARM=%d SP0=%d SP1=%d SP2=%d", (gpioreg[PLLB_CTRL] >> 12) & 0x7, gpioreg[PLLB_CTRL] & 0x3ff, gpioreg[PLLB_CTRL], gpioreg[PLLB_FRAC], gpioreg[PLLB_ARM], gpioreg[PLLB_SP0], gpioreg[PLLB_SP1], gpioreg[PLLB_SP2]); } void log_pllc() { int ANA1_PREDIV = 0; #ifndef RPI4 //prediv not available on BCM2711 see: https://elixir.free-electrons.com/linux/v5.7.19/source/drivers/clk/bcm/clk-bcm2835.c#L529 ANA1_PREDIV = (gpioreg[PLLC_ANA1] >> 14) & 1; #endif int NDIV = (gpioreg[PLLC_CTRL] & 0x3ff) << ANA1_PREDIV; int FRAC = gpioreg[PLLC_FRAC] << ANA1_PREDIV; double clock = CRYSTAL * ((double)NDIV + ((double)FRAC) / ((double)(1 << 20))); log_info("PLLC: %lf, ANA1 = %08x", clock, gpioreg[PLLC_ANA1]); log_info("PLLC: PDIV=%d NDIV=%d CTRL=%08x FRAC=%d CORE2=%d CORE1=%d PER=%d CORE0=%d", (gpioreg[PLLC_CTRL] >> 12) & 0x7, gpioreg[PLLC_CTRL] & 0x3ff, gpioreg[PLLC_CTRL], gpioreg[PLLC_FRAC], gpioreg[PLLC_CORE2], gpioreg[PLLC_CORE1], gpioreg[PLLC_PER], gpioreg[PLLC_CORE0]); } void log_plld() { int ANA1_PREDIV = 0; #ifndef RPI4 //prediv not available on BCM2711 see: https://elixir.free-electrons.com/linux/v5.7.19/source/drivers/clk/bcm/clk-bcm2835.c#L529 ANA1_PREDIV = (gpioreg[PLLD_ANA1] >> 14) & 1; #endif int NDIV = (gpioreg[PLLD_CTRL] & 0x3ff) << ANA1_PREDIV; int FRAC = gpioreg[PLLD_FRAC] << ANA1_PREDIV; double clock = CRYSTAL * ((double)NDIV + ((double)FRAC) / ((double)(1 << 20))); log_info("PLLD: %lf ANA1 = %08x", clock, gpioreg[PLLD_ANA1]); log_info("PLLD: PDIV=%d NDIV=%d CTRL=%08x FRAC=%d DSI0=%d CORE=%d PER=%d DSI1=%d", (gpioreg[PLLD_CTRL] >> 12) & 0x7, gpioreg[PLLD_CTRL] & 0x3ff, gpioreg[PLLD_CTRL], gpioreg[PLLD_FRAC], gpioreg[PLLD_DSI0], gpioreg[PLLD_CORE], gpioreg[PLLD_PER], gpioreg[PLLD_DSI1]); } void log_pllh() { #ifndef RPI4 //pllh not on pi 4 int ANA1_PREDIV = (gpioreg[PLLH_ANA1] >> 11) & 1; //prediv on bit 11 instead of bit 14 for pllh int NDIV = (gpioreg[PLLH_CTRL] & 0x3ff) << ANA1_PREDIV; int FRAC = gpioreg[PLLH_FRAC] << ANA1_PREDIV; double clock = CRYSTAL * ((double)NDIV + ((double)FRAC) / ((double)(1 << 20))); log_info("PLLH: %lf ANA1 = %08x", clock, gpioreg[PLLH_ANA1]); log_info("PLLH: PDIV=%d NDIV=%d CTRL=%08x FRAC=%d AUX=%d RCAL=%d PIX=%d STS=%d", (gpioreg[PLLH_CTRL] >> 12) & 0x7, gpioreg[PLLH_CTRL] & 0x3ff, gpioreg[PLLH_CTRL], gpioreg[PLLH_FRAC], gpioreg[PLLH_AUX], gpioreg[PLLH_RCAL], gpioreg[PLLH_PIX], gpioreg[PLLH_STS]); #endif } void set_pll_frequency(double f, int pll_ctrl, int pll_fract) { // Calculate the new dividers int div = (int) (f / CRYSTAL); int fract = (int) ((double)(1<<20) * (f / CRYSTAL - (double) div)); // Sanity check the range of the fractional divider (it should actually always be in range) if (fract < 0) { log_warn("PLL fraction < 0"); fract = 0; } if (fract > (1<<20) - 1) { log_warn("PLL fraction > 1"); fract = (1<<20) - 1; } // Read the existing values int old_ctrl = gpioreg[pll_ctrl]; int old_div = old_ctrl & 0x3ff; int old_fract = gpioreg[pll_fract]; // Check if there's been a change if (div != old_div || fract != old_fract) { #ifdef USE_PLLC // Flush the UART, as the Core Clock is about to change RPI_AuxMiniUartFlush(); #endif // Update the integer divider if (div != old_div) { gpioreg[pll_ctrl] = CM_PASSWORD | (old_ctrl & 0x00FFFC00) | div; } // Update the fractional divider if (fract != old_fract) { gpioreg[pll_fract] = CM_PASSWORD | fract; } // Re-read the integer divider if it's changed if (div != old_div) { int new_ctrl = gpioreg[pll_ctrl]; int new_div = new_ctrl & 0x3ff; if (new_div == div) { //log_debug(" New int divider: %d", new_div); } else { log_warn("Failed to write int divider: wrote %d, read back %d", div, new_div); } } // Re-read the fraction divider if it's changed if (fract != old_fract) { int new_fract = gpioreg[pll_fract]; if (new_fract == fract) { //log_debug(" New fract divider: %d", new_fract); } else { log_warn("Failed to write fract divider: wrote %d, read back %d", fract, new_fract); } } } } void set_hsync_threshold() { if (capinfo->vsync_type == VSYNC_INTERLACED) { equalising_threshold = EQUALISING_THRESHOLD * cpuspeed / 1000; // if explicitly selecting interlaced then support filtering equalising pulses field_type_threshold = FIELD_TYPE_THRESHOLD_AMIGA * cpuspeed / 1000; } else { equalising_threshold = 100 * cpuspeed / 1000; // otherwise only filter very small pulses (0.1us) which might be glitches field_type_threshold = FIELD_TYPE_THRESHOLD_BBC * cpuspeed / 1000; } if (capinfo->vsync_type == VSYNC_BLANKING) { normal_hsync_threshold = BLANKING_HSYNC_THRESHOLD * cpuspeed / 1000; } else { normal_hsync_threshold = NORMAL_HSYNC_THRESHOLD * cpuspeed / 1000; } if (parameters[F_AUTO_SWITCH] == AUTOSWITCH_MODE7 && hsync_width < hsync_threshold_switch) { hsync_threshold = BBC_HSYNC_THRESHOLD * cpuspeed / 1000; } else { hsync_threshold = normal_hsync_threshold; } } int calibrate_sampling_clock(int profile_changed) { int a = 13; // Default values for the Beeb clkinfo.clock = 16000000; clkinfo.line_len = 1024.0f; //log_plla(); //log_pllb(); //log_pllc(); //log_plld(); // Update from configuration geometry_get_clk_params(&clkinfo); log_info(" clkinfo.clock = %d Hz", clkinfo.clock); log_info(" clkinfo.line_len = %f", clkinfo.line_len); log_info(" clkinfo.clock_ppm = %d ppm", clkinfo.clock_ppm); if (capinfo->vsync_type == VSYNC_BLANKING) { // set to longest value for initial measurement which works with all systems hsync_threshold = BLANKING_HSYNC_THRESHOLD * cpuspeed / 1000; } else { hsync_threshold = NORMAL_HSYNC_THRESHOLD * cpuspeed / 1000; } int nlines = MEASURE_NLINES; // Measure over N=100 lines nlines_ref_ns = nlines * (int) (1e9 * clkinfo.line_len / ((double) clkinfo.clock)); nlines_time_ns = (int)((double) measure_n_lines(nlines) * 1000 / cpuspeed); set_hsync_threshold(); // set to correct value after initial measurement log_info(" Nominal %3d lines = %d ns", nlines, nlines_ref_ns); log_info(" Actual %3d lines = %d ns", nlines, nlines_time_ns); ppm_range = PLL_PPM_LO; ppm_range_count = 0; double error = (double) nlines_time_ns / (double) nlines_ref_ns; clock_error_ppm = ((error - 1.0) * 1e6); log_info(" Clock error = %d PPM", clock_error_ppm); nominal_cpld_clock = clkinfo.clock * cpld->get_divider(); if (profile_changed) { old_clock = clkinfo.clock; if (capinfo->mode7) { old_clock = old_clock * 16 / 12; } } if ((clkinfo.clock_ppm > 0 && abs(clock_error_ppm) > clkinfo.clock_ppm) || (sync_detected == 0)) { if (old_clock > 0 && sub_profiles_available(parameters[F_PROFILE]) == 0) { log_warn("PPM error too large, using previous clock"); new_clock = old_clock * cpld->get_divider(); if (capinfo->mode7) { log_warn("Compensating for mode 7"); new_clock = new_clock * 12 / 16; } } else { log_warn("PPM error too large, using nominal clock"); new_clock = nominal_cpld_clock; } } else { new_clock = (unsigned int) (((double) nominal_cpld_clock) / error); } if (new_clock > 196500000) { new_clock = 196500000; log_warn("Clock exceeds 196Mhz - Limiting to 196Mhz"); } adjusted_clock = new_clock / cpld->get_divider(); old_clock = adjusted_clock; if (capinfo->mode7) { old_clock = old_clock * 16 / 12; } log_info(" Error adjusted clock = %d Hz", adjusted_clock); // Pick the best value for pll_freq and gpclk_divisor #ifdef RPI4 // assumes PLLD selected prediv = 0; pll_scale = PLLD_PER_VALUE; int pll_core = PLLD_CORE_VALUE - 1; min_pll_freq = MIN_PLL_FREQ; // defined at the top max_pll_freq = MAX_PLL_FREQ; // defined at the top gpclk_divisor = max_pll_freq / pll_scale / new_clock; pll_freq = new_clock * pll_scale * gpclk_divisor ; if (pll_freq < min_pll_freq || pll_freq > max_pll_freq) { pll_scale = PLLD_PER_VALUE + 1; pll_core = PLLD_CORE_VALUE; max_pll_freq = MAX_PLL_FREQ + MAX_PLL_EXTENSION; gpclk_divisor = max_pll_freq / pll_scale / new_clock; pll_freq = new_clock * pll_scale * gpclk_divisor ; log_info(" Using extended max frequency"); } if (gpioreg[PLLD_PER] != pll_scale) { gpioreg[PLLD_PER] = CM_PASSWORD | (pll_scale ); *CM_PLLD = CM_PASSWORD | ((*CM_PLLD) | CM_PLL_LOADPER); *CM_PLLD = CM_PASSWORD | ((*CM_PLLD) & ~CM_PLL_LOADPER); } if (gpioreg[PLLD_CORE] != pll_core) { gpioreg[PLLD_CORE] = CM_PASSWORD | (pll_core); *CM_PLLD = CM_PASSWORD | ((*CM_PLLD) | CM_PLL_LOADCORE); *CM_PLLD = CM_PASSWORD | ((*CM_PLLD) & ~(CM_PLL_LOADCORE)); } #else prediv = (gpioreg[ANA1] >> 14) & 1; pll_scale = gpioreg[PER]; min_pll_freq = MIN_PLL_FREQ; // defined at the top max_pll_freq = MAX_PLL_FREQ; // defined at the top gpclk_divisor = max_pll_freq / pll_scale / new_clock; pll_freq = new_clock * pll_scale * gpclk_divisor ; #endif log_info(" Target PLL frequency = %u Hz, prediv = %d, PER = %d", pll_freq, prediv, gpioreg[PER]); // sanity check if (pll_freq < min_pll_freq) { log_warn("**** PLL clock out of range, default to min (%u Hz)", min_pll_freq); pll_freq = MAX_PLL_FREQ; gpclk_divisor = DEFAULT_GPCLK_DIVISOR; } else if (pll_freq > max_pll_freq) { log_warn("**** PLL clock out of range, default to max (%u Hz)", max_pll_freq); pll_freq = MAX_PLL_FREQ; gpclk_divisor = DEFAULT_GPCLK_DIVISOR; } log_info(" Actual PLL frequency = %u Hz", pll_freq); log_info(" GPCLK Divisor = %u", gpclk_divisor); // If the clock has changed from it's previous value, then actually change it if (pll_freq != old_pll_freq) { set_pll_frequency(((double) (pll_freq >> prediv)) / 1e6, PLL_CTRL, PLL_FRAC); #if defined(USE_PLLC) int sys_clk_divider = 3; switch (_get_hardware_id()) { case 2: sys_clk_divider = 4; break; case 4: sys_clk_divider = 5; break; default: sys_clk_divider = 3; // should be 4 for Pi 1 depending on core clock speed break; } // Reinitialize the UART as the Core Clock has changed RPI_AuxMiniUartInit_With_Freq(115200, 8, pll_freq / pll_scale / sys_clk_divider); #endif // And remember for next time old_pll_freq = pll_freq; } // TODO: this should be superfluous (as the GPU is not changing the core clock) // However, if we remove it, the next osd_update_palette() call hangs get_clock_rate(CORE_CLK_ID); // Finally, set the new divisor log_debug("Setting up divisor"); init_gpclk(GPCLK_SOURCE, gpclk_divisor); log_debug("Done setting up divisor"); // Remeasure the hsync time nlines_time_ns = (int)((double) measure_n_lines(nlines) * 1000 / cpuspeed); // Remeasure the vsync time vsync_time_ns = (int)((double)measure_vsync() * 1000 / cpuspeed); if (vsync_retry_count) log_info("Vsync retry count = %d", vsync_retry_count); // sanity check measured values as noise on the sync input results in nonsensical values that can cause a crash if (vsync_time_ns < (frame_minimum << 1) || nlines_time_ns < (line_minimum * nlines)) { log_info("Sync times too short, clipping: %d,%d : %d,%d", vsync_time_ns,nlines_time_ns, frame_timeout << 1, line_timeout * nlines ); vsync_time_ns = frame_timeout << 1; nlines_time_ns = line_timeout * nlines; } // Instead, calculate the number of lines per frame double lines_per_2_vsyncs_double = ((double) vsync_time_ns) / (((double) nlines_time_ns) / ((double) nlines)); one_line_time_ns = nlines_time_ns / nlines; if (geometry_get_value(VSYNC_TYPE) == VSYNC_FORCE_INTERLACE) { interlaced = 1; } else { // If number of lines is odd, then we must be interlaced interlaced = ((int)(lines_per_2_vsyncs_double + 0.5)) % 2; } one_vsync_time_ns = vsync_time_ns >> 1; lines_per_vsync = ((int) (lines_per_2_vsyncs_double + 0.5) >> 1); lines_per_2_vsyncs = (int) (lines_per_2_vsyncs_double + 0.5); calculated_vsync_time_ns = ((double)lines_per_2_vsyncs * nlines_time_ns / MEASURE_NLINES); // calculate vertical period from measured hsync period (two frames / fields so ~40ms) // Log it capinfo->detected_sync_type &= ~SYNC_BIT_INTERLACED; if (interlaced) { capinfo->detected_sync_type |= SYNC_BIT_INTERLACED; log_info(" Lines per frame = %d, (%g)", lines_per_2_vsyncs, lines_per_2_vsyncs_double); log_info("Actual frame time = %d ns (interlaced), line time = %d ns", vsync_time_ns, one_line_time_ns); } else { log_info(" Lines per frame = %d, (%g)", lines_per_vsync, lines_per_2_vsyncs_double / 2); log_info("Actual frame time = %d ns (non-interlaced), line time = %d ns", one_vsync_time_ns, one_line_time_ns); } vsync_width_lines = (vsync_width + (one_line_time_ns >> 1)) / one_line_time_ns; if (vsync_width_lines < 1) { vsync_width_lines = 1; } if (vsync_width_lines > (lines_per_vsync >> 2)) { // if large value then likely measuring inverted sync so set limit on that vsync_width_lines = 4; //vsync_width_lines = lines_per_vsync >> 2; } log_info("Vsync width = %dns, (%d lines)", vsync_width, vsync_width_lines); // Invalidate the current vlock mode to force an updated, as vsync_time_ns will have changed current_genlock_mode = -1; return a; } static void recalculate_hdmi_clock(int genlock_mode, int genlock_adjust) { static double last_f2 = 0.0f; static double error = 1.0f; // The very first time we get called, vsync_time_ns has not been set // so exit gracefully if (vsync_time_ns == 0) { return; } // Dump the PLLH registers //log_pllh(); #ifdef RPI4 static int offset_only = 0; static double divider = 1; if (pllh_clock == 0) { offset_only = (pi4_hdmi0_regs[PI4_HDMI0_RM_OFFSET] & 0x80000000); pllh_clock = (double) (pi4_hdmi0_regs[PI4_HDMI0_RM_OFFSET] & 0x7fffffff); divider = (double) ((pi4_hdmi0_regs[PI4_HDMI0_DIVIDER] & 0x0000ff00) >> 8); } #else int PLLH_ANA1_PREDIV = ((gpioreg[PLLH_ANA1] >> 11) & 1) ? 2 : 1; //prediv on bit 11 instead of bit 14 for pllh // Grab the original PLLH frequency once, at it's original value if (pllh_clock == 0) { pllh_clock = (CRYSTAL * ((double)(gpioreg[PLLH_CTRL] & 0x3ff) + ((double)gpioreg[PLLH_FRAC]) / ((double)(1 << 20)))) * PLLH_ANA1_PREDIV; } #endif //for (int i = 0; i < 32; i++) { // log_debug(" PIXELVALVE2[%2d]: %08x", i, *(PIXELVALVE2_BASE + i)); //} // Dump the PIXELVALVE2 registers //log_debug(" PIXELVALVE2_HORZA: %08x", *PIXELVALVE2_HORZA); //log_debug(" PIXELVALVE2_HORZB: %08x", *PIXELVALVE2_HORZB); //log_debug(" PIXELVALVE2_VERTA: %08x", *PIXELVALVE2_VERTA); //log_debug(" PIXELVALVE2_VERTB: %08x", *PIXELVALVE2_VERTB); // Work out the htotal and vtotal by summing the four 16-bit values: // A[31:16] - back porch width in pixels // A[15: 0] - synch width in pixels // B[31:16] - front porch width in pixels // B[15: 0] - active line width in pixels #if defined(RPI4) uint32_t htotal = ((*PIXELVALVE2_HORZA) + (*PIXELVALVE2_HORZB)) << 1; #else uint32_t htotal = (*PIXELVALVE2_HORZA) + (*PIXELVALVE2_HORZB); #endif htotal = (htotal + (htotal >> 16)) & 0xFFFF; uint32_t vtotal = (*PIXELVALVE2_VERTA) + (*PIXELVALVE2_VERTB); vtotal = (vtotal + (vtotal >> 16)) & 0xFFFF; //log_debug(" H-Total: %d pixels", htotal); //log_debug(" V-Total: %d pixels", vtotal); // PLLH seems to use a fixed divider to generate the pixel clock int fixed_divider = 10; #if defined(RPI4) // conversion of pllh_clock to pixel clock for pi4 derived from // https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/vc4/vc4_hdmi_phy.c#L161 // https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/vc4/vc4_hdmi_phy.c#L189 double pixel_clock = pllh_clock * CRYSTAL / 0x200000 / divider / fixed_divider; #else //log_debug(" Fixed divider: %d", fixed_divider); // 720x576@50 PLLH: PDIV=1 NDIV=56 FRAC=262144 AUX=256 RCAL=256 PIX=4 STS=526655 // 1920x1080@50 PLLH: PDIV=1 NDIV=77 FRAC=360448 AUX=256 RCAL=256 PIX=1 STS=526655 // An additional divider is used to get very low pixel clock rates ^ int additional_divider = gpioreg[PLLH_PIX]; //log_debug("Additional divider: %d", additional_divider); // Calculate the pixel clock double pixel_clock = pllh_clock / ((double) fixed_divider) / ((double) additional_divider); #endif //log_debug(" Pixel Clock: %lf MHz", pixel_clock); // Calculate the error between the HDMI VSync and the Source VSync source_vsync_freq = 2e9 / ((double) vsync_time_ns); display_vsync_freq = 1e6 * pixel_clock / ((double) htotal) / ((double) vtotal); if (display_vsync_freq < 35.0f) { //allow genlock to work with half frame rate modes display_vsync_freq *= 2; half_frame_rate = 1; } int ppm_limit = 50000; if (parameters[F_GENLOCK_ADJUST] == GENLOCK_ADJUST_50_60_2) { ppm_limit = 20000; } else if (parameters[F_GENLOCK_ADJUST] == GENLOCK_ADJUST_50_60_1) { ppm_limit = 10000; } vlock_limited = 0; double nominal_vsync_freq = 1.0f / ((double) clkinfo.lines_per_frame * clkinfo.line_len / (double)clkinfo.clock); //double nominal_vsync_freq = 50; //if (source_vsync_freq >= 55) nominal_vsync_freq = 60; int error_ppm; hdmi_error_ppm = (int) (1000000.0f * ((nominal_vsync_freq / source_vsync_freq) - 1.0f)); if (abs(hdmi_error_ppm) > ppm_limit && (parameters[F_GENLOCK_ADJUST] == GENLOCK_ADJUST_50_60_5 || parameters[F_GENLOCK_ADJUST] == GENLOCK_ADJUST_50_60_2 || parameters[F_GENLOCK_ADJUST] == GENLOCK_ADJUST_50_60_1)) { //error = display_vsync_freq / nominal_vsync_freq; vlock_limited = 1; } else { error = display_vsync_freq / source_vsync_freq; } error_ppm = (int) (1000000.0f * (error - 1.0f)); double f2 = pllh_clock; if (genlock_mode != HDMI_ORIGINAL && source_vsync_freq >= 48) { f2 /= error; f2 /= 1.0 + ((double) (genlock_adjust * GENLOCK_PPM_STEP) / 1000000); } // Sanity check HDMI pixel clock if (strchr(resolution_name, '@') != 0) { //custom res file with @ in its name? if (parameters[F_GENLOCK_ADJUST] != GENLOCK_ADJUST_FULL && abs(error_ppm) > ppm_limit) { f2 = pllh_clock; vlock_limited = 1; hdmi_error_ppm = error_ppm; } } else { switch (force_genlock_range) { default: case GENLOCK_RANGE_NORMAL: case GENLOCK_RANGE_INHIBIT: case GENLOCK_RANGE_SET_DEFAULT: if (abs(error_ppm) > ppm_limit) { f2 = pllh_clock; vlock_limited = 1; hdmi_error_ppm = error_ppm; } break; case GENLOCK_RANGE_EDID: case GENLOCK_RANGE_FORCE_LOW: if (source_vsync_freq_hz < 48 || error_ppm < -ppm_limit) { //don't go below 48Hz or more than 5% above 60 Hz f2 = pllh_clock; vlock_limited = 1; hdmi_error_ppm = error_ppm; } break; case GENLOCK_RANGE_FORCE_ALL: //no limits above 60Hz, still limited below 48Hz if (source_vsync_freq_hz < 48) { f2 = pllh_clock; vlock_limited = 1; hdmi_error_ppm = error_ppm; } break; } } #if defined(RPI4) pixel_clock = f2 * CRYSTAL / 0x200000 / divider / fixed_divider; #else pixel_clock = f2 / ((double) fixed_divider) / ((double) additional_divider); #endif if (pixel_clock < MIN_PIXEL_CLOCK) { log_debug("Pixel clock of %.2lf MHz is too low; leaving unchanged", pixel_clock); f2 = pllh_clock; vlock_limited = 1; } else if (pixel_clock > MAX_PIXEL_CLOCK) { log_debug("Pixel clock of %.2lf MHz is too high; leaving unchanged", pixel_clock); f2 = pllh_clock; vlock_limited = 1; } //log_debug(" Source vsync freq: %lf Hz (measured)", source_vsync_freq); //log_debug("Display vsync freq: %lf Hz", display_vsync_freq); //log_debug(" Vsync error: %lf ppm", hdmi_error_ppm); //log_debug(" Original PLLH: %lf MHz", pllh_clock); //log_debug(" Target PLLH: %lf MHz", f2); source_vsync_freq_hz = (int) (source_vsync_freq + 0.5); info_display_vsync_freq = 1e6 * pixel_clock / ((double) htotal) / ((double) vtotal); info_display_vsync_freq_hz = (int) (info_display_vsync_freq + 0.5); if (f2 != last_f2) { if (genlock_mode == HDMI_EXACT) { #if defined(RPI4) double current_pllh_clock = (double) (pi4_hdmi0_regs[PI4_HDMI0_RM_OFFSET] & 0x7fffffff); #else double current_pllh_clock = (CRYSTAL * ((double)(gpioreg[PLLH_CTRL] & 0x3ff) + ((double)gpioreg[PLLH_FRAC]) / ((double)(1 << 20)))) * PLLH_ANA1_PREDIV; #endif int ppm_diff = (int)((1 - (current_pllh_clock / f2)) * 1000000); int genlock_speed; switch(parameters[F_GENLOCK_SPEED]) { case GENLOCK_SPEED_SLOW: genlock_speed = 333; break; case GENLOCK_SPEED_MEDIUM: genlock_speed = 1000; break; default: case GENLOCK_SPEED_FAST: genlock_speed = 2000; break; } //log_info("%d", ppm_diff); if (abs(ppm_diff) > genlock_speed && abs(ppm_diff) < GENLOCK_SLEW_RATE_THRESHOLD) { restricted_slew_rate = 1; if (ppm_diff >= 0) { f2 = current_pllh_clock + (current_pllh_clock * genlock_speed / 1000000); } else { f2 = current_pllh_clock - (current_pllh_clock * genlock_speed / 1000000); } //log_info("N%d", (int)((1 - (current_pllh_clock / f2)) * 1000000)); } else { restricted_slew_rate = 0; } } if (sync_detected && last_sync_detected && last_but_one_sync_detected) { //&& (abs(hdmi_error_ppm) < 20000 || abs(hdmi_error_ppm) > 100000) last_f2 = f2; #if defined(RPI4) pi4_hdmi0_regs[PI4_HDMI0_RM_OFFSET] = ((int) f2) | offset_only; #else set_pll_frequency(f2 / PLLH_ANA1_PREDIV, PLLH_CTRL, PLLH_FRAC); #endif } } // Dump the the actual PLL frequency //log_debug(" Final PLLH: %lf MHz", (double) CRYSTAL * ((double)(gpioreg[PLLH_CTRL] & 0x3ff) + ((double)gpioreg[PLLH_FRAC]) / ((double)(1 << 20)))); //log_pllh(); } int __attribute__ ((aligned (64))) recalculate_hdmi_clock_line_locked_update(int force) { static int framecount = 0; static int genlock_adjust = 0; static int last_vlock = -1; static int thresholds[GENLOCK_MAX_STEPS] = GENLOCK_THRESHOLDS; static double line_total = 0; static int line_count = 0; static double frame_total = 0; static int frame_count = 0; static int log_flag = 0; static int last = 0x80000000; if (last != jitter_offset) { log_info("Jit%d", jitter_offset); last = jitter_offset; if (parameters[F_GENLOCK_MODE] != HDMI_EXACT) { // Return 0 if genlock disabled return 0; } else { // Return 1 if genlock enabled but not yet locked // Return 2 if genlock enabled and locked return 1 + genlocked; } } /* static int last_debug = -1; if (last_debug != debug_value) { log_info("%08X", debug_value); last_debug = debug_value; if (parameters[F_GENLOCK_MODE] != HDMI_EXACT) { // Return 0 if genlock disabled return 0; } else { // Return 1 if genlock enabled but not yet locked // Return 2 if genlock enabled and locked return 1 + genlocked; } } */ if (!force) { if (sync_detected && last_sync_detected && last_but_one_sync_detected) { line_total += (double)total_hsync_period * 1000 * MEASURE_NLINES / ((double)capinfo->nlines - 1) / (double)cpuspeed; //adjust to MEASURE_NLINES in ns, total will be > 32 bits line_count++; if (vsync_period >= vsync_comparison_lo && vsync_period <= vsync_comparison_hi) { // using the measured vertical period is preferable but when menu is on screen or buttons being pressed the value might be wrong by multiple fields frame_total += (double) vsync_period * 2 * 1000 / cpuspeed ; // if measured value is within window then use it (in ZX80/81 the values are always different to calculated due to one 4.7us shorter line) frame_count++; //log_info("%d %d",vsync_time_ns, vsync_period << 1); } if (line_count >= AVERAGE_VSYNC_TOTAL) { //average over AVERAGE_VSYNC_TOTAL frames (~5 secs) if (frame_count != 0) { //will be AVERAGE_VSYNC_TOTAL or will be less if menus are used due to frame drops vsync_time_ns = (int) (frame_total / (double) frame_count); //log_info("%d", frame_count); frame_total = 0; frame_count = 0; //log_info("%d %d",vsync_time_ns, calculated_vsync_time_ns); } else { vsync_time_ns = calculated_vsync_time_ns; log_flag = 1; } double recalc_nlines_time_ns = line_total / (double) line_count; //get average new line time line_count = 0; line_total = 0; int ppm_lo = (int)(((double) nlines_time_ns * ppm_range / 1000000) + 0.5); if (ppm_lo < 1) ppm_lo = 1; int diff = abs(nlines_time_ns - recalc_nlines_time_ns); //log_info("%d, %d %d %d %d", diff, nlines_time_ns, recalc_nlines_time_ns, ppm_lo, ppm_hi); if (diff >= ppm_lo) { //log_info("%d, %d", ppm_lo, ppm_hi); double error = (double) recalc_nlines_time_ns / (double) nlines_ref_ns; clock_error_ppm = ((error - 1.0) * 1e6); if (clkinfo.clock_ppm == 0 || abs(clock_error_ppm) <= clkinfo.clock_ppm) { new_clock = (unsigned int) (((double) nominal_cpld_clock) / error); if (new_clock > 195000000) new_clock = 195000000; adjusted_clock = new_clock / cpld->get_divider(); old_clock = adjusted_clock; if (capinfo->mode7) { old_clock = old_clock * 16 / 12; } pll_freq = new_clock * pll_scale * gpclk_divisor ; set_pll_frequency(((double) (pll_freq >> prediv)) / 1e6, PLL_CTRL, PLL_FRAC); old_pll_freq = pll_freq; nlines_time_ns = (int) recalc_nlines_time_ns; one_line_time_ns = nlines_time_ns / MEASURE_NLINES; calculated_vsync_time_ns = ((double)lines_per_2_vsyncs * recalc_nlines_time_ns / MEASURE_NLINES); // calculate vertical period from measured hsync period (two frames / fields so ~40ms) if (ppm_range != PLL_PPM_LO) { if (log_flag) { log_info("*VPLL%1d", ppm_range); } else { log_info("*PLL%1d", ppm_range); } } else { if (log_flag) { log_info("*VPLL"); } else { log_info("*PLL"); } } log_flag = 0; if (ppm_range_count < PLL_RESYNC_THRESHOLD_HI) { ppm_range_count++; } // return without doing any genlock processing to avoid two log messages in one field. if (parameters[F_GENLOCK_MODE] != HDMI_EXACT) { // Return 0 if genlock disabled return 0; } else { // Return 1 if genlock enabled but not yet locked // Return 2 if genlock enabled and locked return 1 + genlocked; } } } } } else { line_count = 0; line_total = 0; frame_count = 0; frame_total = 0; } } else { line_count = 0; line_total = 0; frame_count = 0; frame_total = 0; last_vlock = 0x80000000; genlocked = 0; return 0; } // lock_fail = 0; if (sync_detected && last_sync_detected && last_but_one_sync_detected) { int adjustment = 0; if (capinfo->nlines >= GENLOCK_NLINES_THRESHOLD) { adjustment = 1; } if (parameters[F_GENLOCK_MODE] != HDMI_EXACT || vlock_limited != 0) { genlocked = 0; target_difference = 0; resync_count = 0; genlock_adjust = 0; switch (parameters[F_GENLOCK_MODE]) { case HDMI_SLOW_2000PPM: genlock_adjust = 6; break; case HDMI_SLOW_1000PPM: genlock_adjust = 3; break; case HDMI_FAST_1000PPM: genlock_adjust = -3; break; case HDMI_FAST_2000PPM: genlock_adjust = -6; break; } if (last_vlock != parameters[F_GENLOCK_MODE] || vlock_limited != 0) { recalculate_hdmi_clock(parameters[F_GENLOCK_MODE], genlock_adjust); last_vlock = parameters[F_GENLOCK_MODE]; framecount = 0; } } else { int max_steps = GENLOCK_MAX_STEPS; int locked_threshold = GENLOCK_LOCKED_THRESHOLD; int frame_delay = GENLOCK_FRAME_DELAY; if (parameters[F_GENLOCK_SPEED] == GENLOCK_SPEED_MEDIUM) { max_steps >>= 1; locked_threshold--; frame_delay <<= 1; } else { if (parameters[F_GENLOCK_SPEED] == GENLOCK_SPEED_SLOW) { max_steps = 1; locked_threshold = 1; frame_delay <<= 1; } } signed int difference = (vsync_line >> adjustment) - ((total_lines >> adjustment) - parameters[F_GENLOCK_LINE]); if (abs(difference) > (total_lines >> (adjustment + 1))) { difference = -difference; } if (genlocked == 1 && abs(difference) >= thresholds[locked_threshold]) { genlocked = 0; if (difference >= 0) { target_difference = -2; } else { target_difference = 2; } if (abs(difference) > thresholds[locked_threshold]) { log_info("UnLock"); resync_count = 0; target_difference = 0; // lock_fail = 1; } else { log_info("Sync%02d", ++resync_count); if (resync_count >= 99) { resync_count = 0; } } } if(framecount == 0) { int new_genlock_adjust = genlock_adjust; if (genlocked == 0) { if (difference - target_difference == 0) { if (genlock_adjust < 0) { new_genlock_adjust++; } if (genlock_adjust > 0) { new_genlock_adjust--; } if (new_genlock_adjust == 0) { genlocked = 1; target_difference = 0; log_info("Locked"); if (ppm_range_count <= PLL_RESYNC_THRESHOLD_LO && ppm_range > PLL_PPM_LO) { ppm_range--; } else { if (ppm_range_count >= PLL_RESYNC_THRESHOLD_HI && ppm_range < PLL_PPM_LO_LIMIT) { ppm_range++; } } ppm_range_count = 0; } } else { if (difference >= target_difference) { int threshold = 0; if (genlock_adjust >= 0 && genlock_adjust < max_steps) { threshold = thresholds[genlock_adjust]; } if (genlock_adjust < max_steps && difference > threshold) { new_genlock_adjust++; } if (genlock_adjust > 1 && difference <= thresholds[genlock_adjust - 1]) { new_genlock_adjust--; } } else { int threshold = 0; if (genlock_adjust <= 0 && genlock_adjust > -max_steps) { threshold = -thresholds[-genlock_adjust]; } if (genlock_adjust > -max_steps && difference < threshold) { new_genlock_adjust--; } if (genlock_adjust < -1 && difference >= -thresholds[-(genlock_adjust + 1)]) { new_genlock_adjust++; } } } if (new_genlock_adjust != genlock_adjust || last_vlock != HDMI_EXACT || restricted_slew_rate) { recalculate_hdmi_clock(HDMI_EXACT, new_genlock_adjust); last_vlock = HDMI_EXACT; genlock_adjust = new_genlock_adjust; framecount = frame_delay; //log_debug("%4d,%4d,%4d,%4d,%4d,%4d", genlocked, parameters[F_GENLOCK_LINE], vsync_line, difference, thresholds[abs(genlock_adjust)], genlock_adjust); } } } } } if (framecount != 0) { framecount --; } if (parameters[F_GENLOCK_MODE] != HDMI_EXACT) { // Return 0 if genlock disabled return 0; } else { // Return 1 if genlock enabled but not yet locked // Return 2 if genlock enabled and locked return 1 + genlocked; } } // Configure PLLA so we can use it as a sampling clock source // // The logic to configure PLLA conmes from the Linux Kernel clk-bcm2835 // driver, specifically the following functions: // - bcm2835_pll_divider_off // - bcm2835_pll_divider_set_rate // - bcm2835_pll_divider_on // https://elixir.bootlin.com/linux/v4.4.70/source/drivers/clk/bcm/clk-bcm2835.c static void configure_pll(int per_divider, int core_divider, int *cm_pll, int pll_per, int pll_core, int core_check) { // Disable PLL_PER divider *cm_pll = CM_PASSWORD | (((*cm_pll) & ~CM_PLL_LOADPER) | CM_PLL_HOLDPER); gpioreg[pll_per] = CM_PASSWORD | (A2W_PLL_CHANNEL_DISABLE); if (core_check) { // Disable PLLA_CORE divider (to check it's not being used!) *cm_pll = CM_PASSWORD | (((*cm_pll) & ~CM_PLL_LOADCORE) | CM_PLL_HOLDCORE); gpioreg[pll_core] = CM_PASSWORD | (A2W_PLL_CHANNEL_DISABLE); } // Set the pll_per divider to the value passed in if (per_divider != 0) { gpioreg[pll_per] = CM_PASSWORD | (per_divider); *cm_pll = CM_PASSWORD | ((*cm_pll) | CM_PLL_LOADPER); *cm_pll = CM_PASSWORD | ((*cm_pll) & ~CM_PLL_LOADPER); } if (core_divider != 0) { gpioreg[pll_core] = CM_PASSWORD | (core_divider); *cm_pll = CM_PASSWORD | ((*cm_pll) | CM_PLL_LOADCORE); *cm_pll = CM_PASSWORD | ((*cm_pll) & ~(CM_PLL_LOADCORE)); } // Enable PLL PER divider gpioreg[pll_per] = CM_PASSWORD | (gpioreg[pll_per] & ~A2W_PLL_CHANNEL_DISABLE); *cm_pll = CM_PASSWORD | (*cm_pll & ~CM_PLL_HOLDPER); } int eight_bit_detected() { return supports8bit; } int new_DAC_detected() { return newanalog; } int any_DAC_detected() { return DAC_detected; } int mono_board_detected() { return mono_detected; } void set_vsync_psync(int state) { cpld->set_vsync_psync(state); } void calculate_cpu_timings() { static int old_cpuspeed = 0; cpuspeed = get_clock_rate(ARM_CLK_ID)/1000000; if (cpuspeed != old_cpuspeed) { log_info("CPU speed detected as: %d Mhz", cpuspeed); old_cpuspeed = cpuspeed; } elk_lo_field_sync_threshold = ELK_LO_FIELD_SYNC_THRESHOLD * cpuspeed / 1000; elk_hi_field_sync_threshold = ELK_HI_FIELD_SYNC_THRESHOLD * cpuspeed / 1000; odd_threshold = ODD_THRESHOLD * cpuspeed / 1000; even_threshold = EVEN_THRESHOLD * cpuspeed / 1000; hsync_threshold_switch = (BBC_HSYNC_THRESHOLD - 400) * cpuspeed / 1000; set_hsync_threshold(); frame_minimum = (int)((double)FRAME_MINIMUM * cpuspeed / 1000); frame_timeout = (int)((double)FRAME_TIMEOUT * cpuspeed / 1000); line_minimum = LINE_MINIMUM * cpuspeed / 1000; hsync_scroll = (HSYNC_SCROLL_LO * cpuspeed / 1000) | ((HSYNC_SCROLL_HI * cpuspeed / 1000) << 16); line_timeout = LINE_TIMEOUT * cpuspeed / 1000; //not currently used } static void init_hardware() { int i; // Initialize hardware cycle counter _init_cycle_counter(); RPI_SetGpioPinFunction(MODE7_PIN, FS_OUTPUT); RPI_SetGpioValue(MODE7_PIN, 1); get_hdisplay(); //forces early reboot if no hdmi connector fitted #ifdef RPI4 *EMMC_LEGACY = *EMMC_LEGACY | 2; //bit enables legacy SD controller #endif RPI_SetGpioPullUpDown(SP_DATA_MASK | SW1_MASK | SW2_MASK | SW3_MASK | VERSION_MASK | SP_CLK_MASK, GPIO_PULLUP); RPI_SetGpioPullUpDown(STROBE_MASK | MUX_MASK, GPIO_PULLDOWN); supports8bit = 0; newanalog = 0; simple_detected = 0; RPI_SetGpioPinFunction(PSYNC_PIN, FS_INPUT); RPI_SetGpioPinFunction(CSYNC_PIN, FS_INPUT); RPI_SetGpioPinFunction(SW1_PIN, FS_INPUT); RPI_SetGpioPinFunction(SW2_PIN, FS_INPUT); RPI_SetGpioPinFunction(SW3_PIN, FS_INPUT); RPI_SetGpioPinFunction(STROBE_PIN, FS_INPUT); RPI_SetGpioPinFunction(SP_DATA_PIN, FS_INPUT); RPI_SetGpioPinFunction(SP_CLKEN_PIN, FS_INPUT); RPI_SetGpioPinFunction(MUX_PIN, FS_INPUT); for (i = 0; i < 12; i++) { RPI_SetGpioPinFunction(PIXEL_BASE + i, FS_INPUT); } delay_in_arm_cycles(1000000); //~1ms delay if (RPI_GetGpioValue(SP_DATA_PIN) == 0) { supports8bit = 1; } RPI_SetGpioPinFunction(SP_DATA_PIN, FS_OUTPUT); RPI_SetGpioPinFunction(SP_CLKEN_PIN, FS_OUTPUT); //force CLKEN low so that V5 is initially detected as V3 RPI_SetGpioValue(SP_DATA_PIN, 0); RPI_SetGpioValue(SP_CLKEN_PIN, 0); RPI_SetGpioPinFunction(VERSION_PIN, FS_OUTPUT); RPI_SetGpioPinFunction(SP_CLK_PIN, FS_OUTPUT); RPI_SetGpioValue(VERSION_PIN, 1); //force VERSION PIN high to help weak pullup RPI_SetGpioValue(SP_CLK_PIN, 1); //force SP_CLK_PIN high to help weak pullup delay_in_arm_cycles(100000); //~100uS settle delay RPI_SetGpioPinFunction(VERSION_PIN, FS_INPUT); //make VERSION PIN input RPI_SetGpioPinFunction(SP_CLK_PIN, FS_INPUT); //make SP_CLK_PIN input delay_in_arm_cycles(1000000); //~1ms delay to allow strong pulldown to take effect if fitted int version_state = RPI_GetGpioValue(VERSION_PIN); delay_in_arm_cycles(10000); version_state |= RPI_GetGpioValue(VERSION_PIN); delay_in_arm_cycles(10000); version_state |= RPI_GetGpioValue(VERSION_PIN); // read three times to be sure as it maybe picking up noise from adjacent tracks with just weak pullup int sp_clk_state = RPI_GetGpioValue(SP_CLK_PIN); delay_in_arm_cycles(10000); sp_clk_state |= RPI_GetGpioValue(SP_CLK_PIN); delay_in_arm_cycles(10000); sp_clk_state |= RPI_GetGpioValue(SP_CLK_PIN); // read three times to be sure as it maybe picking up noise from adjacent tracks with just weak pullup if (!version_state) { simple_detected = 1; } else { if (RPI_GetGpioValue(STROBE_PIN) == 1) { // if high then must be V4, if low then could be (V1-3) or V5 newanalog = 1; } else { RPI_SetGpioValue(SP_CLKEN_PIN, 1); //force CLKEN HIGH to detect V5 analog board delay_in_arm_cycles(1000000); //~1ms settle delay if (RPI_GetGpioValue(STROBE_PIN) == 1) { newanalog = 2; } } } RPI_SetGpioPinFunction(VERSION_PIN, FS_OUTPUT); RPI_SetGpioPinFunction(SP_CLK_PIN, FS_OUTPUT); RPI_SetGpioPinFunction(MODE7_PIN, FS_OUTPUT); RPI_SetGpioPinFunction(SP_CLK_PIN, FS_OUTPUT); RPI_SetGpioPinFunction(LED1_PIN, FS_OUTPUT); RPI_SetGpioValue(VERSION_PIN, 1); RPI_SetGpioValue(MODE7_PIN, 1); RPI_SetGpioValue(MUX_PIN, 0); RPI_SetGpioValue(SP_CLK_PIN, 1); RPI_SetGpioValue(SP_DATA_PIN, 0); RPI_SetGpioValue(SP_CLKEN_PIN, 0); RPI_SetGpioValue(LED1_PIN, 0); // active high // This line enables IRQ interrupts // Enable smi_int which is IRQ 48 // https://github.com/raspberrypi/firmware/issues/67 RPI_GetIrqController()->Enable_IRQs_2 = (1 << VSYNCINT); // Configure the GPCLK pin as a GPCLK RPI_SetGpioPinFunction(GPCLK_PIN, FS_ALT5); if (simple_detected) { log_info("Simple board detected"); } else { log_info("CPLD board detected"); if (supports8bit) { log_info("8/12 bit board detected"); } else { log_info("6 bit board detected"); } if (newanalog == 1) { log_info("Issue 4 analog board detected"); } else if (newanalog == 2) { log_info("Issue 5 analog board detected"); } } if (sp_clk_state == 0 && simple_detected == 0 && supports8bit) { log_info("Mono / lumacode board detected"); mono_detected = 1; } else { log_info("Standard board detected"); } log_info("Using %s as the sampling clock", PLL_NAME); // Log all the PLL values log_plla(); log_pllb(); log_pllc(); log_plld(); log_pllh(); #if defined(USE_PLLA) // Enable the PLLA_PER divider configure_pll(PLLA_PER_VALUE, 0, (int*)CM_PLLA, PLLA_PER, PLLA_CORE, 1); log_plla(); #endif #if defined(USE_PLLA4) // Enable the PLLA_PER divider configure_pll(PLLA_PER_VALUE, 0, (int*)CM_PLLA, PLLA_PER, PLLA_CORE, 0); log_plla(); #endif #if defined(USE_PLLC4) configure_pll(PLLC_PER_VALUE, 0, (int*)CM_PLLC, PLLC_PER, PLLC_CORE1, 0); log_pllc(); #endif #if defined(USE_PLLD4) configure_pll(PLLD_PER_VALUE, PLLD_CORE_VALUE, (int*)CM_PLLD, PLLD_PER, PLLD_CORE, 0); log_plld(); #endif // The divisor us now the same for both modes log_debug("Setting up divisor"); init_gpclk(GPCLK_SOURCE, DEFAULT_GPCLK_DIVISOR); log_debug("Done setting up divisor"); calculate_cpu_timings(); // Initialize the cpld after the gpclk generator has been started cpld_init(); // Initialize the On-Screen Display osd_init(); // Initialise the info system with cached values (as we break the GPU property interface) init_info(); #ifdef DEBUG dump_useful_info(); #endif } int read_cpld_version(){ int cpld_version_id = 0; if (!simple_detected) { for (int i = PIXEL_BASE + 11; i >= PIXEL_BASE; i--) { cpld_version_id <<= 1; cpld_version_id |= RPI_GetGpioValue(i) & 1; } if ((cpld_version_id >> VERSION_DESIGN_BIT) == DESIGN_SIMPLE) { // if cpld reads back SIMPLE id then probably a pre-programmed CPLD that has to be erased so set to unknown instead to stop simple mode settings like single button mode cpld_version_id = (DESIGN_UNKNOWN << 8) | (cpld_version_id & 0xff ); } } else { cpld_version_id = (DESIGN_SIMPLE << 8); // reads as V0.0 } return cpld_version_id; } static void cpld_init() { // have to set mux to 0 to allow analog detection to work // so clock out 32 bits of 0 into register chain as later CPLDs have mux as a register bit // int sp = 0x1180; //15 bits sets the rate bits to 12bit capture for testing simple mode with amiga int sp = 0x200000; //24 bits sets the rate bits to 12bit capture for testing simple mode with amiga for (int i = 0; i < 24; i++) { RPI_SetGpioValue(SP_DATA_PIN, sp & 1); delay_in_arm_cycles_cpu_adjust(250); RPI_SetGpioValue(SP_CLKEN_PIN, 1); delay_in_arm_cycles_cpu_adjust(250); RPI_SetGpioValue(SP_CLK_PIN, 0); delay_in_arm_cycles_cpu_adjust(250); RPI_SetGpioValue(SP_CLK_PIN, 1); delay_in_arm_cycles_cpu_adjust(250); RPI_SetGpioValue(SP_CLKEN_PIN, 0); delay_in_arm_cycles_cpu_adjust(250); sp >>= 1; } RPI_SetGpioValue(MUX_PIN, 0); // have to set mux to 0 to allow analog detection to work (GPIO on older cplds) // Assert the active low version pin RPI_SetGpioValue(VERSION_PIN, 0); delay_in_arm_cycles_cpu_adjust(1000); RPI_SetGpioPinFunction(STROBE_PIN, FS_OUTPUT); RPI_SetGpioValue(STROBE_PIN, 0); delay_in_arm_cycles_cpu_adjust(10000); // The CPLD now outputs an identifier and version number on the 12-bit pixel quad bus cpld_version_id = read_cpld_version(); int cpld_design = cpld_version_id >> VERSION_DESIGN_BIT; int cpld_version = cpld_version_id & 0xff; int check_delete_file = check_file(FORCE_BLANK_FILE, FORCE_BLANK_FILE_MESSAGE); // true means file was missing and has been recreated //int force_update_file = test_file(FORCE_UPDATE_FILE); // true means file is present // Set the appropriate cpld "driver" based on the version if (cpld_design == DESIGN_BBC) { RPI_SetGpioPinFunction(STROBE_PIN, FS_INPUT); if (cpld_version <= 0x20) { cpld = &cpld_bbcv10v20; } else if (cpld_version <= 0x23) { cpld = &cpld_bbcv21v23; } else if (cpld_version <= 0x24) { cpld = &cpld_bbcv24; } else if (cpld_version <= 0x62) { cpld = &cpld_bbcv30v62; } else { cpld = &cpld_bbc; } } else if (cpld_design == DESIGN_ATOM) { RPI_SetGpioPinFunction(STROBE_PIN, FS_INPUT); cpld = &cpld_atom; } else if (cpld_design == DESIGN_YUV_ANALOG) { DAC_detected = 1; cpld = &cpld_yuv_analog; } else if (cpld_design == DESIGN_YUV_TTL) { cpld = &cpld_yuv_ttl; } else if (cpld_design == DESIGN_RGB_TTL) { RPI_SetGpioValue(STROBE_PIN, 1); delay_in_arm_cycles_cpu_adjust(1000); if (cpld_design == DESIGN_RGB_ANALOG) { // if STROBE_PIN (GPIO22) has an effect on the version ID (P19) it means the RGB cpld has been programmed into the BBC board cpld = &cpld_null_6bit; cpld_fail_state = CPLD_WRONG; } else { if (cpld_version >= 0x70 && cpld_version < 0x80) { cpld = &cpld_rgb_ttl_24mhz; } else { cpld = &cpld_rgb_ttl; } } RPI_SetGpioPinFunction(STROBE_PIN, FS_INPUT); // set STROBE PIN back to an input as P19 will be an ouput when VERSION_PIN set back to 1 } else if (cpld_design == DESIGN_RGB_ANALOG) { DAC_detected = 1; if (cpld_version >= 0x70 && cpld_version < 0x80) { cpld = &cpld_rgb_analog_24mhz; } else { cpld = &cpld_rgb_analog; } } else if (cpld_design == DESIGN_SIMPLE) { cpld = &cpld_simple; } else { log_info("Unknown CPLD: identifier = %03x", cpld_version_id); if (cpld_version_id == 0xfff) { cpld_fail_state = CPLD_BLANK; } else { cpld_fail_state = CPLD_UNKNOWN; } cpld = &cpld_null; RPI_SetGpioPinFunction(STROBE_PIN, FS_INPUT); } if (!test_file(FORCE_UPDATE_FILE) && cpld_fail_state == CPLD_NORMAL) { log_info("CPLD update file not detected"); if (cpld_design == DESIGN_RGB_TTL || cpld_design == DESIGN_RGB_ANALOG) { if (cpld_version == BBC_VERSION || cpld_version == RGB_VERSION) { log_info("CPLD_UPDATE state not set"); } else { cpld_fail_state = CPLD_UPDATE; log_info("CPLD_UPDATE state set"); } } if (cpld_design == DESIGN_YUV_TTL || cpld_design == DESIGN_YUV_ANALOG) { if ( cpld_version == YUV_VERSION ) { log_info("CPLD_UPDATE state not set."); } else { cpld_fail_state = CPLD_UPDATE; log_info("CPLD_UPDATE state set."); } } check_file(FORCE_UPDATE_FILE, FORCE_UPDATE_FILE_MESSAGE); } if (cpld_version >= 0xa0 && cpld_design != DESIGN_NULL) { //cpld version >=0xa0 is currently invalid so probably a CPLD with other code but this may change if hex version numbers are ever used cpld_fail_state = CPLD_UNKNOWN; } int keycount = key_press_reset() & 0x07; //all buttons pressed during reset log_info("Keycount = %d", keycount); if (keycount == 7) { switch(cpld_design) { case DESIGN_BBC: cpld = &cpld_null_3bit; break; case DESIGN_RGB_TTL: case DESIGN_RGB_ANALOG: case DESIGN_YUV_TTL: case DESIGN_YUV_ANALOG: cpld = &cpld_null_6bit; break; case DESIGN_ATOM: cpld = &cpld_null_atom; break; case DESIGN_SIMPLE: cpld = &cpld_null_simple; break; default: cpld = &cpld_null; break; } cpld_fail_state = CPLD_MANUAL; RPI_SetGpioPinFunction(STROBE_PIN, FS_INPUT); } if (cpld_version == 0x7f && cpld_design != DESIGN_NULL) { //invalid version number of 0x7F is read when cpld board not connected log_info("Invalid version number 0x7F detected. No CPLD board fitted"); cpld_fail_state = CPLD_NOT_FITTED; cpld_design = DESIGN_RGB_TTL; cpld_version_id = (cpld_design << VERSION_DESIGN_BIT) | cpld_version; cpld = &cpld_null_6bit; supports8bit = 1; RPI_SetGpioPinFunction(STROBE_PIN, FS_INPUT); } // Release the active low version pin. This will damage the cpld if YUV is programmed into a BBC board but not RGB due to above safety test delay_in_arm_cycles_cpu_adjust(1000); RPI_SetGpioValue(VERSION_PIN, 1); log_info("CPLD Design: %s", cpld->name); log_info("CPLD Version: %x.%x", (cpld_version_id >> VERSION_MAJOR_BIT) & 0x0f, (cpld_version_id >> VERSION_MINOR_BIT) & 0x0f); //erase CPLD before anything that might cause a lockup with a corrupt CPLD if (check_delete_file) { log_info("Early erase of CPLD"); update_cpld(BLANK_FILE, 0); log_info("Early erase of CPLD failed"); } // Initialize the CPLD's default sampling points cpld->init(cpld_version_id); // Initialize the geometry geometry_init(cpld_version_id); } int extra_flags() { int extra = 0; if (cpld->old_firmware_support()) { extra |= BIT_OLD_FIRMWARE_SUPPORT; } if (parameters[F_AUTO_SWITCH] != AUTOSWITCH_MODE7) { extra |= BIT_NO_H_SCROLL; } if (!parameters[F_SCANLINES] || ((capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT) == 0) || (capinfo->mode7) || osd_active()) { extra |= BIT_NO_SCANLINES; } if (osd_active()) { extra |= BIT_OSD; } if (cpld->get_sync_edge()) { extra |= BIT_HSYNC_EDGE; } return extra; } static void start_core(int core, func_ptr func) { printf("starting core %d\r\n", core); #ifdef RPI4 *(unsigned int *)(0xff80008c + 0x10 * core) = (unsigned int) func; #else *(unsigned int *)(0x4000008c + 0x10 * core) = (unsigned int) func; #endif asm ( "sev" ); } // ============================================================= // Public methods // ============================================================= int diff_N_frames(capture_info_t *capinfo, int n, int elk) { int result = 0; // Calculate frame differences, broken out by channel and by sample point (A..F) int *by_offset = diff_N_frames_by_sample(capinfo, n, elk); // Collapse the offset dimension for (int i = 0; i < NUM_OFFSETS; i++) { result += by_offset[i]; } return result; } int *diff_N_frames_by_sample(capture_info_t *capinfo, int n, int elk) { unsigned int ret; // NUM_OFFSETS is 6 (Sample Offset A..Sample Offset F) static int sum[NUM_OFFSETS]; static int min[NUM_OFFSETS]; static int max[NUM_OFFSETS]; static int diff[NUM_OFFSETS]; static int linediff[NUM_OFFSETS]; for (int i = 0; i < NUM_OFFSETS; i++) { sum[i] = 0; min[i] = INT_MAX; max[i] = INT_MIN; } #ifdef INSTRUMENT_CAL unsigned int t; unsigned int t_capture = 0; unsigned int t_memcpy = 0; unsigned int t_compare = 0; #endif unsigned int flags = extra_flags() | BIT_CALIBRATE | (2 << OFFSET_NBUFFERS); uint32_t bpp = capinfo->bpp; uint32_t pix_mask; uint32_t osd_mask; uint32_t mask_BIT_OSD = -1; switch (bpp) { case 4: pix_mask = 0x00000007; osd_mask = 0x77777777; capinfo->ncapture = (capinfo->video_type != VIDEO_PROGRESSIVE) ? 2 : 1; break; case 8: pix_mask = 0x0000007F; osd_mask = 0x77777777; capinfo->ncapture = (capinfo->video_type != VIDEO_PROGRESSIVE) ? 2 : 1; break; case 16: default: pix_mask = 0x00000fff; osd_mask = 0xffffffff; //if (capinfo->video_type == VIDEO_INTERLACED && capinfo->detected_sync_type & SYNC_BIT_INTERLACED) { // mask_BIT_OSD = ~BIT_OSD; //} capinfo->ncapture = 1; break; } //capinfo->video_type == VIDEO_INTERLACED && capinfo->detected_sync_type & SYNC_BIT_INTERLACED geometry_get_fb_params(capinfo); // required as calibration sets delay to 0 and the 2 high bits of that adjust the h offset #ifdef INSTRUMENT_CAL t = _get_cycle_counter(); #endif // Grab an initial frame ret = rgb_to_fb(capinfo, flags & mask_BIT_OSD); #ifdef INSTRUMENT_CAL t_capture += _get_cycle_counter() - t; #endif int ytotal = capinfo->nlines << (capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT); int ystep = 1; if (capinfo->video_type == VIDEO_PROGRESSIVE && (capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT)) { ystep = 2; } for (int i = 0; i < n; i++) { #ifdef INSTRUMENT_CAL t = _get_cycle_counter(); #endif // Save the last frame memcpy((void *)last, (void *)(capinfo->fb + ((ret >> OFFSET_LAST_BUFFER) & 3) * capinfo->height * capinfo->pitch), capinfo->height * capinfo->pitch); #ifdef INSTRUMENT_CAL t_memcpy += _get_cycle_counter() - t; t = _get_cycle_counter(); #endif // Grab the next frame ret = rgb_to_fb(capinfo, flags & mask_BIT_OSD); #ifdef INSTRUMENT_CAL t_capture += _get_cycle_counter() - t; t = _get_cycle_counter(); #endif // memcpy((void *)latest, (void *)(capinfo->fb + ((ret >> OFFSET_LAST_BUFFER) & 3) * capinfo->height * capinfo->pitch), capinfo->height * capinfo->pitch); for (int j = 0; j < NUM_OFFSETS; j++) { diff[j] = 0; } int sequential_error_count = 0; int total_error_count = 0; int single_pixel_count = 0; int last_error_line = 0; poll_soft_reset(); // Compare the frames: start 4 lines down from the first line and end 4 lines before the end to avoid any glitchy lines when osd on. uint32_t *fbp = (uint32_t *)(capinfo->fb + ((ret >> OFFSET_LAST_BUFFER) & 3) * capinfo->height * capinfo->pitch + (capinfo->v_adjust + 4) * capinfo->pitch); uint32_t *lastp = (uint32_t *)last + (capinfo->v_adjust + 4) * (capinfo->pitch >> 2); for (int y = 0; y < (ytotal - 4); y += ystep) { for (int j = 0; j < NUM_OFFSETS; j++) { linediff[j] = 0; } switch (bpp) { case 4: { if (capinfo->mode7) { single_pixel_count += scan_for_single_pixels_4bpp(fbp, capinfo->pitch); } for (int x = 0; x < capinfo->pitch; x += 4) { uint32_t d = (*fbp++ & osd_mask) ^ (*lastp++ & osd_mask); //uint32_t d = osd_get_equivalence(*fbp++ & osd_mask) ^ osd_get_equivalence(*lastp++ & osd_mask); int index = (x << 1) % NUM_OFFSETS; //2 pixels per byte while (d) { if (d & pix_mask) { linediff[index]++; } d >>= 4; index = (index + 1) % NUM_OFFSETS; } } break; } case 8: for (int x = 0; x < capinfo->pitch; x += 4) { uint32_t d = (*fbp++ & osd_mask) ^ (*lastp++ & osd_mask); //uint32_t d = osd_get_equivalence(*fbp++ & osd_mask) ^ osd_get_equivalence(*lastp++ & osd_mask); int index = x % NUM_OFFSETS; //1 pixel per byte while (d) { if (d & pix_mask) { linediff[index]++; } d >>= 8; index = (index + 1) % NUM_OFFSETS; } } break; case 16: default: { if (capinfo->mode7) { single_pixel_count += scan_for_single_pixels_12bpp(fbp, capinfo->pitch); } scan_for_diffs_12bpp(fbp, lastp, capinfo->pitch, linediff); fbp += (capinfo->pitch >> 2); lastp += (capinfo->pitch >> 2); break; } } int line_errors = 0; for (int j = 0; j < NUM_OFFSETS; j++) { line_errors += linediff[j]; diff[j] += linediff[j]; } if (line_errors != 0) { if (last_error_line != y - ystep && sequential_error_count != 0) { sequential_error_count = 0; //log_info("Error count not sequential on line: %d, %d",last_error_line, y); } total_error_count++; sequential_error_count++; last_error_line = y; } if (ystep != 1) { fbp += capinfo->pitch >> 2; lastp += capinfo->pitch >> 2; } } //log_info("Total=%d, Sequential=%d", total_error_count, sequential_error_count); int line_threshold = 16; // max is 16 (12 lines on 6847) if (parameters[F_AUTO_SWITCH] == AUTOSWITCH_MODE7) { line_threshold = 2; // reduce to minimum on beeb } //if sequential lines had errors then probably a flashing cursor so ignore the errors if (sequential_error_count <= line_threshold && (total_error_count - sequential_error_count) == 0) { for (int j = 0; j < NUM_OFFSETS; j++) { diff[j] = 0; } } #ifdef INSTRUMENT_CAL t_compare += _get_cycle_counter() - t; #endif // At this point the diffs correspond to the sample points in // an unusual order: A F C B E D // // This happens for three reasons: // - the CPLD starts with sample point B, so you get B C D E F A // - the firmware skips the first quad, so you get F A B C D E // - if in 4bpp mode the frame buffer swaps odd and even pixels, so you get A F C B E D // Mutate the result to correctly order the sample points: // Then the downstream algorithms don't have to worry //log_info("count = %d, %d", single_pixel_count); if (capinfo->mode7 && single_pixel_count == 0) { //generate fake diff errors if thick verticals detected diff[0] += 1000; diff[1] += 1000; diff[2] += 1000; diff[3] += 1000; diff[4] += 1000; diff[5] += 1000; } if (bpp == 4) { // A F C B E D => A B C D E F int f = diff[1]; int b = diff[3]; int d = diff[5]; diff[1] = b; diff[3] = d; diff[5] = f; } else { // F A B C D E => A B C D E F int f = diff[0]; diff[0] = diff[1]; diff[1] = diff[2]; diff[2] = diff[3]; diff[3] = diff[4]; diff[4] = diff[5]; diff[5] = f; } // Accumulate the result for (int j = 0; j < NUM_OFFSETS; j++) { sum[j] += diff[j]; if (diff[j] < min[j]) { min[j] = diff[j]; } if (diff[j] > max[j]) { max[j] = diff[j]; } } } #if 0 for (int i = 0; i < NUM_OFFSETS; i++) { log_debug("offset %d diff: sum = %d min = %d, max = %d", i, sum[i], min[i], max[i]); } #endif #ifdef INSTRUMENT_CAL log_debug("t_capture total = %d, mean = %d ", t_capture, t_capture / (n + 1)); log_debug("t_compare total = %d, mean = %d ", t_compare, t_compare / n); log_debug("t_memcpy total = %d, mean = %d ", t_memcpy, t_memcpy / n); log_debug("total = %d", t_capture + t_compare + t_memcpy); #endif return sum; } #define MODE7_CHAR_WIDTH 12 signed int analyze_mode7_alignment(capture_info_t *capinfo) { if (!capinfo->mode7) { return -1; } log_info("Testing mode 7 alignment"); // mode 7 character is 12 pixels wide int counts[MODE7_CHAR_WIDTH]; // bit offset pixels 0..7 int px_offset_map[] = {4, 0, 12, 8, 20, 16, 28, 24}; unsigned int flags = extra_flags() | BIT_CALIBRATE | (2 << OFFSET_NBUFFERS); // Capture two fields capinfo->ncapture = 2; // Grab a frame int ret = rgb_to_fb(capinfo, flags); // Work out the base address of the frame buffer that was used uint32_t *fbp = (uint32_t *)(capinfo->fb + ((ret >> OFFSET_LAST_BUFFER) & 3) * capinfo->height * capinfo->pitch + capinfo->v_adjust * capinfo->pitch + capinfo->h_adjust); // Initialize the counters for (int i = 0; i < MODE7_CHAR_WIDTH; i++) { counts[i] = 0; } // Count the pixels uint32_t *fbp_line; for (int line = 0; line < capinfo->nlines << (capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT); line++) { int index = 0; fbp_line = fbp; for (int byte = 0; byte < (capinfo->chars_per_line << 2); byte += 4) { uint32_t word = *fbp_line++; int *offset = px_offset_map; for (int i = 0; i < 8; i++) { int px = (word >> (*offset++)) & 7; if (px) { counts[index]++; } index = (index + 1) % MODE7_CHAR_WIDTH; } } fbp += capinfo->pitch >> 2; } // Log the raw counters for (int i = 0; i < MODE7_CHAR_WIDTH; i++) { log_info("counter %2d = %d", i, counts[i]); } // A typical distribution looks like // INFO: counter 0 = 647 // INFO: counter 1 = 573 // INFO: counter 2 = 871 // INFO: counter 3 = 878 // INFO: counter 4 = 572 // INFO: counter 5 = 653 // INFO: counter 6 = 869 // INFO: counter 7 = 742 // INFO: counter 8 = 2 // INFO: counter 9 = 2 // INFO: counter 10 = 906 // INFO: counter 11 = 1019 // There should be a two pixel minima int min_count = INT_MAX; int min_i = -1; for (int i = 0; i < MODE7_CHAR_WIDTH; i++) { int c = counts[i] + counts[(i + 1) % MODE7_CHAR_WIDTH]; if (c < min_count) { min_count = c; min_i = i; } } log_info("minima at index: %d", min_i); // That minima should occur in pixels 0 and 1, so compute a delay to make this so return (MODE7_CHAR_WIDTH - min_i); } #define DEFAULT_CHAR_WIDTH 8 signed int analyze_default_alignment(capture_info_t *capinfo) { if (parameters[F_AUTO_SWITCH] != AUTOSWITCH_MODE7) { return -1; } log_info("Testing default alignment"); // mode 0 character is 8 pixels wide int counts[DEFAULT_CHAR_WIDTH]; // bit offset pixels 0..7 int px_offset_map[] = {4, 0, 12, 8, 20, 16, 28, 24}; unsigned int flags = extra_flags() | BIT_CALIBRATE | (2 << OFFSET_NBUFFERS); // Capture two fields capinfo->ncapture = 1; // Grab a frame int ret = rgb_to_fb(capinfo, flags); // Work out the base address of the frame buffer that was used uint32_t *fbp = (uint32_t *)(capinfo->fb + ((ret >> OFFSET_LAST_BUFFER) & 3) * capinfo->height * capinfo->pitch + capinfo->v_adjust * capinfo->pitch + capinfo->h_adjust); // Initialize the counters for (int i = 0; i < DEFAULT_CHAR_WIDTH; i++) { counts[i] = 0; } // Count the pixels uint32_t *fbp_line; if (capinfo->bpp == 4) { for (int line = 0; line < capinfo->nlines << (capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT); line++) { int index = 0; fbp_line = fbp; for (int byte = 0; byte < (capinfo->chars_per_line << 2); byte += 4) { uint32_t word = *fbp_line++; int *offset = px_offset_map; for (int i = 0; i < 8; i++) { int px = (word >> (*offset++)) & 7; if (px) { counts[index]++; } index = (index + 1) % DEFAULT_CHAR_WIDTH; } } fbp += capinfo->pitch >> 2; } } else { for (int line = 0; line < capinfo->nlines << (capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT); line++) { int index = 0; fbp_line = fbp; for (int byte = 0; byte < (capinfo->chars_per_line << 2); byte += 4) { uint32_t word = *fbp_line++; for (int i = 0; i < 4; i++) { int px = (word >> (i*8)) & 0x7f; if (px) { counts[index]++; } index = (index + 1) % DEFAULT_CHAR_WIDTH; } word = *fbp_line++; for (int i = 0; i < 4; i++) { int px = (word >> (i*8)) & 0x7f; if (px) { counts[index]++; } index = (index + 1) % DEFAULT_CHAR_WIDTH; } } fbp += capinfo->pitch >> 2; } } // Log the raw counters for (int i = 0; i < DEFAULT_CHAR_WIDTH; i++) { log_info("counter %2d = %d", i, counts[i]); } // A typical distribution looks like // INFO: counter 0 = 878 // INFO: counter 1 = 740 // INFO: counter 2 = 212 // INFO: counter 3 = 2 // INFO: counter 4 = 1036 // INFO: counter 5 = 1224 // INFO: counter 6 = 648 // INFO: counter 7 = 706 // There should be a one pixel minima int min_count = INT_MAX; int min_i = -1; for (int i = 0; i < DEFAULT_CHAR_WIDTH; i++) { int c = counts[i]; if (c < min_count) { min_count = c; min_i = i; } } log_info("minima at index: %d", min_i); // That minima should occur in pixels 0 and 1, so compute a delay to make this so return DEFAULT_CHAR_WIDTH - min_i; } #if 0 int total_N_frames(capture_info_t *capinfo, int n, int elk) { int sum = 0; int min = INT_MAX; int max = INT_MIN; #ifdef INSTRUMENT_CAL unsigned int t; unsigned int t_capture = 0; unsigned int t_compare = 0; #endif unsigned int flags = extra_flags() | BIT_CALIBRATE | (2 << OFFSET_NBUFFERS); // In mode 0..6, capture one field // In mode 7, capture two fields capinfo->ncapture = capinfo->video_type != VIDEO_PROGRESSIVE ? 2 : 1; for (int i = 0; i < n; i++) { int total = 0; // Grab the next frame ret = rgb_to_fb(capinfo, flags); #ifdef INSTRUMENT_CAL t_capture += _get_cycle_counter() - t; t = _get_cycle_counter(); #endif // Compare the frames uint32_t *fbp = (uint32_t *)(capinfo->fb + ((ret >> OFFSET_LAST_BUFFER) & 3) * capinfo->height * capinfo->pitch); for (int j = 0; j < capinfo->height * capinfo->pitch; j += 4) { uint32_t f = *fbp++; // Mask out OSD f &= 0x77777777; while (f) { if (f & 0x0F) { total++; } f >>= 4; } } #ifdef INSTRUMENT_CAL t_compare += _get_cycle_counter() - t; #endif // Accumulate the result sum += total; if (total < min) { min = total; } if (total > max) { max = total; } } int mean = sum / n; log_debug("total: sum = %d mean = %d, min = %d, max = %d", sum, mean, min, max); #ifdef INSTRUMENT_CAL log_debug("t_capture total = %d, mean = %d ", t_capture, t_capture / (n + 1)); log_debug("t_compare total = %d, mean = %d ", t_compare, t_compare / n); log_debug("total = %d", t_capture + t_compare + t_memcpy); #endif return sum; } #endif void DPMS(int dpms_state) { if (parameters[F_HDMI_MODE_STANDBY] == 1) { log_info("********************DPMS state: %d", dpms_state); // rpi_mailbox_property_t *mp; RPI_PropertyInit(); RPI_PropertyAddTag(TAG_BLANK_SCREEN, dpms_state); RPI_PropertyProcess(); if (capinfo->bpp == 16) { //have to wait for field sync for display list to be updated wait_for_pi_fieldsync(); wait_for_pi_fieldsync(); //delay_in_arm_cycles_cpu_adjust(30000000); // little extra delay //read the index pointer into the display list RAM display_list_index = (uint32_t) *SCALER_DISPLIST1; int dli; do { dli = display_list[display_list_index]; } while (dli == 0xFF000000); display_list[display_list_index] = (dli & ~0x600f) | (PIXEL_ORDER << 13) | PIXEL_FORMAT; } } } #ifdef MULTI_BUFFER void swapBuffer(int buffer) { current_display_buffer = buffer; if (capinfo->bpp == 16) { // directly manipulate the display list in 16BPP mode otherwise display list gets reconstructed int dli = ((int)capinfo->fb | framebuffer_topbits) + (buffer * capinfo->height * capinfo->pitch); do { display_list[display_list_index + display_list_offset] = dli; } while (dli != display_list[display_list_index + display_list_offset]); } else { RPI_PropertyInit(); RPI_PropertyAddTag(TAG_SET_VIRTUAL_OFFSET, 0, capinfo->height * buffer); // Use version that doesn't wait for the response RPI_PropertyProcessNoCheck(); } } #endif int get_vsync_width_lines() { return vsync_width_lines; } int get_current_display_buffer() { if ((capinfo->video_type == VIDEO_PROGRESSIVE || (capinfo->video_type == VIDEO_INTERLACED && !interlaced))) { return current_display_buffer; } else { return 0; } } void set_startup_overscan(int value) { startup_overscan = value; } int get_startup_overscan() { return startup_overscan; } void set_config_overscan(int l, int r, int t, int b) { config_overscan_left = l; config_overscan_right = r; config_overscan_top = t; config_overscan_bottom = b; } void get_config_overscan(int *l, int *r, int *t, int *b) { *l = config_overscan_left; *r = config_overscan_right; *t = config_overscan_top; *b = config_overscan_bottom; } void set_force_genlock_range(int value) { force_genlock_range = value; } void set_auto_workaround_path(char *value, int reboot) { strcpy(auto_workaround_path, value); if (reboot) { reboot_required |= 0x10; file_save_config(resolution_name, parameters[F_REFRESH], parameters[F_SCALING], filtering, parameters[F_FRONTEND], parameters[F_HDMI_MODE], parameters[F_HDMI_AUTO], auto_workaround_path); } } void set_general_reboot() { reboot_required |= 0x40; } void set_filtering(int filter) { filtering = filter; old_filtering = filter; } int get_adjusted_ntscphase() { int phase = parameters[F_NTSC_PHASE]; if (parameters[F_NTSC_QUALITY] == FRINGE_SOFT) { phase |= NTSC_SOFT; } if (parameters[F_NTSC_QUALITY] == FRINGE_MEDIUM) { phase |= NTSC_MEDIUM; } return phase; } int get_core_1_available() { return core_1_available; } int get_one_line_time_ns() { return one_line_time_ns; } int get_sync_detected() { return sync_detected; } int get_lines_per_vsync(int compensate) { if (compensate) { int lines = geometry_get_value(LINES_FRAME); int clock_ppm = geometry_get_value(CLOCK_PPM); int frame_window = (clock_ppm * lines / 1000000) + 2; if (frame_window < 20) frame_window = 20; if (lines_per_vsync >= (lines - frame_window) && lines_per_vsync <= (lines + 1)) { return lines_per_vsync; } else { return lines; } } else { return lines_per_vsync; } } int get_50hz_state() { if (source_vsync_freq_hz == 50) { return vlock_limited; } return -1; } void set_hdmi_auto(int value, int reboot) { parameters[F_HDMI_AUTO] = value; if (reboot == 0) { old_hdmi_auto = parameters[F_HDMI_AUTO]; } else { if (parameters[F_HDMI_AUTO] != old_hdmi_auto) { reboot_required |= 0x20; log_info("Requesting hdmi_auto reboot %d", parameters[F_HDMI_AUTO]); resolution_warning = 1; } else { reboot_required &= ~0x20; resolution_warning = 0; } } if (reboot) { file_save_config(resolution_name, parameters[F_REFRESH], parameters[F_SCALING], filtering, parameters[F_FRONTEND], parameters[F_HDMI_MODE], parameters[F_HDMI_AUTO], auto_workaround_path); } } void set_hdmi(int value, int reboot) { parameters[F_HDMI_MODE] = value; if (reboot == 0) { old_hdmi_mode = parameters[F_HDMI_MODE]; } else { if (parameters[F_HDMI_MODE] != old_hdmi_mode) { reboot_required |= 0x04; log_info("Requesting hdmi_mode reboot %d", parameters[F_HDMI_MODE]); resolution_warning = 1; } else { reboot_required &= ~0x04; resolution_warning = 0; } } if (reboot) { file_save_config(resolution_name, parameters[F_REFRESH], parameters[F_SCALING], filtering, parameters[F_FRONTEND], parameters[F_HDMI_MODE], parameters[F_HDMI_AUTO], auto_workaround_path); } } void set_refresh(int value, int reboot) { parameters[F_REFRESH] = value; if (reboot == 0) { old_refresh = parameters[F_REFRESH]; } else { if (parameters[F_REFRESH] != old_refresh) { reboot_required |= 0x08; log_info("Requesting refresh reboot %d", parameters[F_REFRESH]); resolution_warning = 1; } else { reboot_required &= ~0x08; resolution_warning = 0; } } if (reboot) { reboot_required |= 0x08; file_save_config(resolution_name, parameters[F_REFRESH], parameters[F_SCALING], filtering, parameters[F_FRONTEND], parameters[F_HDMI_MODE], parameters[F_HDMI_AUTO], auto_workaround_path); } } void set_resolution(int value, const char *name, int reboot) { parameters[F_RESOLUTION] = value; strcpy(resolution_name, name); if (reboot == 0) { old_resolution = parameters[F_RESOLUTION]; } else { if (parameters[F_RESOLUTION] != old_resolution) { reboot_required |= 0x01; resolution_warning = 1; } else { reboot_required &= ~0x01; resolution_warning = 0; } } if (reboot) { file_save_config(resolution_name, parameters[F_REFRESH], parameters[F_SCALING], filtering, parameters[F_FRONTEND], parameters[F_HDMI_MODE], parameters[F_HDMI_AUTO], auto_workaround_path); } } void set_scaling(int value, int reboot) { if (value == SCALING_AUTO) { geometry_set_mode(0); int width = geometry_get_value(MIN_H_WIDTH); int height = geometry_get_value(MIN_V_HEIGHT); int h_size = get_hdisplay() - config_overscan_left - config_overscan_right; int v_size = get_vdisplay() - config_overscan_top - config_overscan_bottom; double ratio = (double) h_size / v_size; int h_size43 = h_size; if (ratio > 1.34) { h_size43 = v_size * 4 / 3; } int video_type = geometry_get_value(VIDEO_TYPE); geometry_set_mode(modeset); if ((video_type == VIDEO_TELETEXT && parameters[F_MODE7_SCALING] == SCALING_UNEVEN) // workaround mode 7 width so it looks like other modes ||( video_type != VIDEO_TELETEXT && parameters[F_NORMAL_SCALING] == SCALING_UNEVEN && get_haspect() == 3 && (get_vaspect() == 2 || get_vaspect() == 4))) { width = width * 4 / 3; } if ((width > 340 && h_size43 < 1440 && (h_size43 % width) > (width / 3)) || (parameters[F_AUTO_SWITCH] == AUTOSWITCH_MODE7 && v_size == 1024) || (h_size <= 720 && v_size <= 480 && height > 240) ) { gscaling = GSCALING_MANUAL43; filtering = FILTERING_SOFT; set_auto_name("Auto (Interp. 4:3/Soft)"); osd_refresh(); log_info("Setting 4:3 soft"); } else { gscaling = GSCALING_INTEGER; if ((parameters[F_AUTO_SWITCH] == AUTOSWITCH_MODE7) && (v_size == 600 || v_size == 576)) { filtering = FILTERING_SOFT; set_auto_name("Auto (Integer/Soft)"); osd_refresh(); log_info("Setting Integer soft"); } else { filtering = FILTERING_NEAREST_NEIGHBOUR; set_auto_name("Auto (Integer/Sharp)"); osd_refresh(); log_info("Setting Integer Sharp"); } } } else { switch (value) { default: case SCALING_INTEGER_SHARP: gscaling = GSCALING_INTEGER; filtering = FILTERING_NEAREST_NEIGHBOUR; break; case SCALING_INTEGER_SOFT: gscaling = GSCALING_INTEGER; filtering = FILTERING_SOFT; break; case SCALING_INTEGER_VERY_SOFT: gscaling = GSCALING_INTEGER; filtering = FILTERING_VERY_SOFT; break; case SCALING_FILL43_SOFT: gscaling = GSCALING_MANUAL43; filtering = FILTERING_SOFT; break; case SCALING_FILL43_VERY_SOFT: gscaling = GSCALING_MANUAL43; filtering = FILTERING_VERY_SOFT; break; case SCALING_FILLALL_SOFT: gscaling = GSCALING_MANUAL; filtering = FILTERING_SOFT; break; case SCALING_FILLALL_VERY_SOFT: gscaling = GSCALING_MANUAL; filtering = FILTERING_VERY_SOFT; break; } } parameters[F_SCALING] = value; set_gscaling(gscaling); if (reboot != 0 && filtering != old_filtering) { reboot_required |= 0x02; log_info("Requesting reboot %d", filtering); } else { reboot_required &= ~0x02; } if (reboot == 1 || (reboot == 2 && reboot_required)) { file_save_config(resolution_name, parameters[F_REFRESH], parameters[F_SCALING], filtering, parameters[F_FRONTEND], parameters[F_HDMI_MODE], parameters[F_HDMI_AUTO], auto_workaround_path); } } void set_frontend(int value, int save) { int min = cpld->frontend_info() & 0xffff; int max = cpld->frontend_info() >> 16; if (value >= min && value <= max) { parameters[F_FRONTEND] = value; } else { if (value == 0 || value > max) { parameters[F_FRONTEND] = min; } else { parameters[F_FRONTEND] = max; } } if (save != 0) { file_save_config(resolution_name, parameters[F_REFRESH], parameters[F_SCALING], filtering, parameters[F_FRONTEND], parameters[F_HDMI_MODE], parameters[F_HDMI_AUTO], auto_workaround_path); } cpld->set_frontend(parameters[F_FRONTEND]); } int get_parameter(int parameter) { switch (parameter) { //space for special case handling case F_SCANLINE_LEVEL: { if ((geometry_get_value(FB_SIZEX2) & 1) == 0) { return 0; // returns 0 depending on state of double height } else { return parameters[parameter]; } } break; default: if (parameter < MAX_PARAMETERS) { return parameters[parameter]; } else { return 0; } break; } } void set_parameter(int parameter, int value) { switch (parameter) { //space for special case handling case F_PALETTE: { parameters[parameter] = value; osd_update_palette(); } break; case F_TINT: case F_SAT: case F_CONT: case F_BRIGHT: case F_GAMMA: { parameters[parameter] = value; osd_update_palette(); update_cga16_color(); } break; case F_PROFILE: { parameters[parameter] = value; log_info("Setting profile to %d", value); } break; case F_SUB_PROFILE: { parameters[parameter] = value; log_info("Setting subprofile to %d", value); } break; case F_SAVED_CONFIG: { parameters[parameter] = value; log_info("Setting saved config number to %d", value); } break; case F_PALETTE_CONTROL: { if (parameters[parameter] != value) { parameters[parameter] = value; osd_update_palette(); } } break; case F_NTSC_PHASE: { if (parameters[parameter] != value) { parameters[parameter] = value; osd_update_palette(); update_cga16_color(); } } break; case F_NTSC_TYPE: case F_NTSC_QUALITY: { parameters[parameter] = value; update_cga16_color(); } break; case F_BORDER_COLOUR: case F_SCANLINES: { parameters[parameter] = value; clear = BIT_CLEAR; } break; case F_GENLOCK_MODE: case F_GENLOCK_LINE: { parameters[parameter] = value; recalculate_hdmi_clock_line_locked_update(GENLOCK_FORCE); } break; case F_AUTO_SWITCH: { // Prevent autoswitch (to mode 7) being accidentally with the Atom CPLD, // for example by selecting the BBC_Micro profile, as this results in // an unusable OSD which persists even after cycling power. // // Atom timing looks like Mode 7, but as we don't have 6bpp mode 7 // line capture code, we end up using the default line capture code, // which immediately overwrites the OSD with capture data. But because the // mode7 flag is set, the OSD is not then repainted in the blanking interval. // The end result is the OSD is briefly appears when a button is pressed, // then vanishes, making it very tricky to navigate. // // It might be better to combine this with the cpld->old_firmware() and // rename this to cpld->get_capabilities(). int cpld_ver = (cpld->get_version() >> VERSION_DESIGN_BIT) & 0x0F; if (value == AUTOSWITCH_MODE7 && (cpld_ver == DESIGN_ATOM || cpld_ver == DESIGN_YUV_ANALOG)) { if (parameters[F_AUTO_SWITCH] == (AUTOSWITCH_MODE7 - 1)) { parameters[F_AUTO_SWITCH] = AUTOSWITCH_MODE7 + 1; } else if (parameters[F_AUTO_SWITCH] == (AUTOSWITCH_MODE7 + 1)) { parameters[F_AUTO_SWITCH] = AUTOSWITCH_MODE7 - 1; } else { parameters[F_AUTO_SWITCH] = value; } } else { parameters[F_AUTO_SWITCH] = value; } set_hsync_threshold(); } break; default: if (parameter < MAX_PARAMETERS) { parameters[parameter] = value; } break; } } void set_ntsccolour(int value) { //called from assembler parameters[F_NTSC_COLOUR] = value; } void set_timingset(int value) { //called from assembler parameters[F_TIMING_SET] = value; } void action_calibrate_clocks() { // re-measure vsync and set the core/sampling clocks calibrate_sampling_clock(0); // set the hdmi clock property to match exactly set_parameter(F_GENLOCK_MODE, HDMI_EXACT); } void action_calibrate_auto(int save_message) { // re-measure vsync and set the core/sampling clocks calibrate_sampling_clock(0); // During calibration we do our best to auto-delect an Electron log_debug("Elk mode = %d", elk_mode); for (int c = 0; c < NUM_CAL_PASSES; c++) { cpld->calibrate(capinfo, elk_mode); } if (save_message) { osd_set(11, 0, "Press MENU to save configuration"); osd_set(12, 0, "Press up or down to skip saving"); } else { osd_set(11, 0, "Calibration complete"); osd_set(12, 0, "Press any button to exit"); } last_divider = cpld->get_divider(); } int is_genlocked() { return genlocked; } void calculate_fb_adjustment() { int double_height = capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT; capinfo->v_adjust = (capinfo->height >> double_height) - capinfo->nlines; if (capinfo->v_adjust < 0) { capinfo->v_adjust = 0; } capinfo->v_adjust >>= (double_height ^ 1); capinfo->h_adjust = (capinfo->width >> 3) - capinfo->chars_per_line; if (capinfo->h_adjust < 0) { capinfo->h_adjust = 0; } capinfo->h_adjust = (capinfo->h_adjust >> 1); switch(capinfo->bpp) { case 4: capinfo->h_adjust <<= 2; break; case 8: capinfo->h_adjust <<= 3; break; case 16: capinfo->h_adjust <<= 4; break; } //log_info("adjust=%d, %d", capinfo->h_adjust, capinfo->v_adjust); } void refresh_cpld(){ cpld->update_capture_info(capinfo); } void setup_profile(int profile_changed) { geometry_set_mode(modeset); capinfo->palette_control = parameters[F_PALETTE_CONTROL]; if ((capinfo->palette_control == PALETTECONTROL_NTSCARTIFACT_CGA && parameters[F_NTSC_COLOUR] == 0)) { capinfo->palette_control = PALETTECONTROL_OFF; } capinfo->palette_control |= (get_inhibit_palette_dimming16() << 31); log_debug("Loading sample points"); cpld->set_mode(modeset); log_debug("Done loading sample points"); cpld->update_capture_info(capinfo); geometry_get_fb_params(capinfo); if (parameters[F_AUTO_SWITCH] == AUTOSWITCH_MODE7) { capinfo->detected_sync_type = cpld->analyse(capinfo->sync_type, 0); // skips sync test if BBC and assumes non-inverted composite (saves time during mode changes) } else { capinfo->detected_sync_type = cpld->analyse(capinfo->sync_type, 1); } log_info("Detected polarity state = %X, %s (%s)", capinfo->detected_sync_type, sync_names[capinfo->detected_sync_type & SYNC_BIT_MASK], mixed_names[(capinfo->detected_sync_type & SYNC_BIT_MIXED_SYNC) ? 1 : 0]); cpld->analyse(capinfo->detected_sync_type, 0); //set to detected sync type when measuring sync timing rgb_to_fb(capinfo, extra_flags() | BIT_PROBE); // dummy mode7 probe to setup sync type from capinfo // Measure the frame time and set the sampling clock calibrate_sampling_clock(profile_changed); cpld->analyse(capinfo->sync_type, 0); //restore to profile sync preset if (parameters[F_POWERUP_MESSAGE] && (powerup || osd_timer > 0)) { osd_set(0, 0, " "); //dummy print to turn osd on so interlaced modes can display startup resolution later } geometry_get_fb_params(capinfo); if (parameters[F_AUTO_SWITCH] != AUTOSWITCH_OFF && sub_profiles_available(parameters[F_PROFILE])) { // set window around expected time from sub-profile double line_time = clkinfo.line_len * 1000000000 / (double) clkinfo.clock; int window = (int) ((double) clkinfo.clock_ppm * line_time / 1000000); hsync_comparison_lo = (line_time - window) * cpuspeed / 1000; hsync_comparison_hi = (line_time + window) * cpuspeed / 1000; vsync_comparison_lo = (hsync_comparison_lo * clkinfo.lines_per_frame); vsync_comparison_hi = (hsync_comparison_hi * clkinfo.lines_per_frame); } else { // set window around measured time int window = (int) ((double) clkinfo.clock_ppm * (double) one_line_time_ns / 1000000); int vwindow = (int) ((double) clkinfo.clock_ppm * (double) one_vsync_time_ns / 1000000); hsync_comparison_lo = (one_line_time_ns - window) * cpuspeed / 1000; hsync_comparison_hi = (one_line_time_ns + window) * cpuspeed / 1000; vsync_comparison_lo = (int)((double)(one_vsync_time_ns - vwindow) * cpuspeed / 1000); if (vsync_comparison_lo < frame_minimum) { vsync_comparison_lo = frame_minimum; } vsync_comparison_hi = (int)((double)(one_vsync_time_ns + vwindow) * cpuspeed / 1000); if (vsync_comparison_hi > frame_timeout) { vsync_comparison_hi = frame_timeout; } } if ((capinfo->detected_sync_type & SYNC_BIT_MASK) != (capinfo->sync_type & SYNC_BIT_MASK) || vsync_retry_count == VSYNC_RETRY_MAX) { //if detected sync type doesn't match profile sync type then set windows to 0 so capture will be aborted to try again hsync_comparison_lo = 1; hsync_comparison_hi = 0; vsync_comparison_lo = 1; vsync_comparison_hi = 0; } log_info("Window: H=%d to %d, V=%d to %d", hsync_comparison_lo * 1000 / cpuspeed, hsync_comparison_hi * 1000 / cpuspeed, (int)((double)vsync_comparison_lo * 1000 / cpuspeed) , (int)((double)vsync_comparison_hi * 1000 / cpuspeed)); hsync_comparison_lo *= (capinfo->nlines - 1); //actually measure nlines-1 hsyncs to average out jitter hsync_comparison_hi *= (capinfo->nlines - 1); log_info("Sync=%s, Det-Sync=%s, Det-HS-Width=%d, HS-Thresh=%d", sync_names[capinfo->sync_type & SYNC_BIT_MASK], sync_names[capinfo->detected_sync_type & SYNC_BIT_MASK], hsync_width, hsync_threshold); } void set_status_message(char *msg) { strcpy(status, msg); } void set_helper_flag() { helper_flag = 2; } void rgb_to_hdmi_main() { int result = RET_SYNC_TIMING_CHANGED; // make sure autoswitch works first time int last_modeset; int mode_changed; int fb_size_changed; int active_size_changed; int clk_changed = 0; int ncapture; int last_profile = -1; int last_subprofile = -1; int last_saved_config_number = -1; int last_sync_edge = -1; int last_gscaling = -1; int refresh_osd = 0; char osdline[80]; capture_info_t last_capinfo; clk_info_t last_clkinfo; // Setup defaults (these may be overridden by the CPLD) capinfo = &set_capinfo; capinfo->capture_line = capture_line_normal_3bpp_table; capinfo->v_adjust = 0; capinfo->h_adjust = 0; capinfo->border = 0; capinfo->sync_type = SYNC_BIT_COMPOSITE_SYNC; current_display_buffer = 0; #ifndef USE_ARM_CAPTURE log_info("Starting GPU code"); start_vc(); #endif // Determine initial sync polarity (and correct whether inversion required or not) capinfo->detected_sync_type = cpld->analyse(capinfo->sync_type, 1); log_info("Detected polarity state at startup = %x, %s (%s)", capinfo->detected_sync_type, sync_names[capinfo->detected_sync_type & SYNC_BIT_MASK], mixed_names[(capinfo->detected_sync_type & SYNC_BIT_MIXED_SYNC) ? 1 : 0]); // Determine initial mode cpld->set_mode(MODE_SET1); capinfo->autoswitch = AUTOSWITCH_MODE7; modeset = rgb_to_fb(capinfo, extra_flags() | BIT_PROBE); if (cpld_fail_state != CPLD_NORMAL) { modeset = MODE_SET1; } log_info("modeset = %d", modeset); // Default to capturing indefinitely ncapture = -1; int keycount = key_press_reset(); log_info("Keycount = %d", keycount); //for (int i=0; i<16; i++) { //display_list[(0x3fc0 >> 2) + i]=0x55; //log_info("display memory = %d = %08X", i, display_list[(0x3000 >> 2) + i]); //} if (keycount == 1 || force_genlock_range == GENLOCK_RANGE_SET_DEFAULT) { sw1_power_up = 1; force_genlock_range = GENLOCK_RANGE_INHIBIT; if (simple_detected) { if ((strcmp(resolution_name, DEFAULT_RESOLUTION) != 0) || parameters[F_REFRESH] != AUTO_REFRESH || parameters[F_HDMI_AUTO] != DEFAULT_HDMI_AUTO ) { log_info("Resetting output resolution/refresh to Auto/50Hz-60Hz"); file_save_config(DEFAULT_RESOLUTION, AUTO_REFRESH, DEFAULT_SCALING, DEFAULT_FILTERING, parameters[F_FRONTEND], parameters[F_HDMI_MODE], DEFAULT_HDMI_AUTO, auto_workaround_path); // Wait a while to allow UART time to empty delay_in_arm_cycles_cpu_adjust(200000000); reboot(); } } else { if ((strcmp(resolution_name, DEFAULT_RESOLUTION) != 0) || parameters[F_REFRESH] != DEFAULT_REFRESH || parameters[F_HDMI_AUTO] != DEFAULT_HDMI_AUTO ) { log_info("Resetting output resolution/refresh to Auto/EDID"); file_save_config(DEFAULT_RESOLUTION, DEFAULT_REFRESH, DEFAULT_SCALING, DEFAULT_FILTERING, parameters[F_FRONTEND], parameters[F_HDMI_MODE], DEFAULT_HDMI_AUTO, auto_workaround_path); // Wait a while to allow UART time to empty delay_in_arm_cycles_cpu_adjust(200000000); reboot(); } } } set_scaling(parameters[F_SCALING], 2); resolution_warning = 0; clear = BIT_CLEAR; if (_get_hardware_id() >= _RPI2) { if (get_core_1_available() !=0) { log_info("Second core available"); } else { log_info("Second core NOT available"); } } while (1) { log_info("-----------------------LOOP------------------------"); if (parameters[F_PROFILE] != last_profile || last_saved_config_number != parameters[F_SAVED_CONFIG]) { last_subprofile = -1; } setup_profile(parameters[F_PROFILE] != last_profile || last_subprofile != parameters[F_SUB_PROFILE] || last_saved_config_number != parameters[F_SAVED_CONFIG]); if ((parameters[F_AUTO_SWITCH] != AUTOSWITCH_OFF) && sub_profiles_available(parameters[F_PROFILE]) && ((result & (RET_SYNC_TIMING_CHANGED | RET_SYNC_STATE_CHANGED)) || parameters[F_PROFILE] != last_profile || last_subprofile != parameters[F_SUB_PROFILE] || restart_profile)) { int new_sub_profile = autoswitch_detect(one_line_time_ns, lines_per_vsync, capinfo->detected_sync_type & SYNC_BIT_MASK); if (new_sub_profile >= 0) { if (new_sub_profile != last_subprofile || parameters[F_PROFILE] != last_profile || parameters[F_SAVED_CONFIG] != last_saved_config_number || restart_profile) { set_parameter(F_SUB_PROFILE, new_sub_profile); process_sub_profile(get_parameter(F_PROFILE), new_sub_profile); setup_profile(1); } set_status_message(""); } else { set_status_message("Auto Switch: No profile matched"); log_info("Autoswitch: No profile matched"); } } if (last_subprofile != parameters[F_SUB_PROFILE] || restart_profile) { ntsc_status = (modeset << NTSC_LAST_IIGS_SHIFT) | (parameters[F_NTSC_COLOUR] << NTSC_LAST_ARTIFACT_SHIFT); } geometry_get_fb_params(capinfo); restart_profile = 0; last_divider = cpld->get_divider(); last_sync_edge = cpld->get_sync_edge(); last_profile = parameters[F_PROFILE]; last_subprofile = parameters[F_SUB_PROFILE]; last_saved_config_number = parameters[F_SAVED_CONFIG]; last_gscaling = gscaling; //log_info("Setting up frame buffer"); init_framebuffer(capinfo); //log_info("Done setting up frame buffer"); //log_info("Peripheral base = %08X", _get_peripheral_base()); geometry_get_fb_params(capinfo); capinfo->ncapture = ncapture; calculate_fb_adjustment(); // force recalculation of the HDMI clock (if the genlock_mode property requires this) recalculate_hdmi_clock_line_locked_update(GENLOCK_FORCE); log_info("Screen size = %dx%d", get_hdisplay(), get_vdisplay()); log_info("Pitch=%d, width=%d, height=%d, sizex2=%d, bpp=%d", capinfo->pitch, capinfo->width, capinfo->height, capinfo->sizex2, capinfo->bpp); log_info("chars=%d, nlines=%d, hoffset=%d, voffset=%d, ncapture=%d", capinfo->chars_per_line, capinfo->nlines, capinfo->h_offset, capinfo-> v_offset, capinfo->ncapture); log_info("palctrl=%d, samplewidth=%d, hadjust=%d, vadjust=%d, sync=0x%x", capinfo->palette_control, capinfo->sample_width, capinfo->h_adjust, capinfo->v_adjust, capinfo->sync_type); log_info("detsync=0x%x, vsync=%d, video=%d, ntsc=%d, border=%d, delay=%d", capinfo-> detected_sync_type, capinfo->vsync_type, capinfo->video_type, capinfo->ntscphase, capinfo->border, capinfo->delay); refresh_osd = 1; if (capinfo->border !=0) { clear = BIT_CLEAR; } do { geometry_get_fb_params(capinfo); capinfo->ncapture = ncapture; calculate_fb_adjustment(); if (refresh_osd) { refresh_osd = 0; osd_refresh(); } if (powerup) { int triple = (int)((double) benchmarkRAM(5) * 1000 / cpuspeed / 100000 + 0.5); log_info("ARM: GPIO read = %dns, MBOX read = %dns, Triple MBOX read = %dns (%dns/word)", (int)((double) benchmarkRAM(3) * 1000 / cpuspeed / 100000 + 0.5), (int)((double) benchmarkRAM(4) * 1000 / cpuspeed / 100000 + 0.5), triple, triple / 3); log_info("GPU: GPIO read = %dns, MBOX write = %dns", (int)((double) benchmarkRAM(1) * 1000 / cpuspeed / 100000 + 0.5), (int)((double) benchmarkRAM(2) * 1000 / cpuspeed / 100000 + 0.5)); log_info("RAM: Cached read = %dns, Uncached screen read = %dns", (int)((double) benchmarkRAM(0x2000000) * 1000 / cpuspeed / 100000 + 0.5), (int)((double) benchmarkRAM((int)capinfo->fb) * 1000 / cpuspeed / 100000 + 0.5)); //***********test CGA artifact decode********************* update_cga16_color(); Bit8u pixels[1024]; for(int i=0; i<1024;i++) pixels[i] = 0x09; //put some 4 bit pixel data in the pixel words = a8f0a8f Composite_Process(720/8, pixels, 0); //720 pixels to include some border - call before timing so code & data get cached int startcycle = get_cycle_counter(); Composite_Process(720/8, pixels, 0); //720 pixels to include some border int duration = abs(get_cycle_counter() - startcycle); log_info("Composite_Process 720 pixel artifact decode: = %dns", duration); Composite_Process_Asm(720/8, pixels, 0); //720 pixels to include some border startcycle = get_cycle_counter(); Composite_Process_Asm(720/8, pixels, 0); //720 pixels to include some border duration = abs(get_cycle_counter() - startcycle); log_info("Test_Composite_Process 720 pixel artifact decode: = %dns", duration); //***********end of test CGA artifact decode*************** if (cpld_fail_state == CPLD_MANUAL) { rgb_to_fb(capinfo, extra_flags() | BIT_PROBE); // dummy mode7 probe to setup parms from capinfo osd_set(0, 0, "Release buttons for CPLD recovery menu"); do {} while (key_press_reset() != 0); } // If the CPLD is unprogrammed, operate in a degraded mode that allows the menus to work if (cpld_fail_state != CPLD_NORMAL) { rgb_to_fb(capinfo, extra_flags() | BIT_PROBE); // dummy mode7 probe to setup parms from capinfo status[0] = 0; // Immediately load the CPLD Update Menu (renamed to CPLD Recovery Menu) osd_show_cpld_recovery_menu(cpld_fail_state); while (1) { if (status[0] != 0) { osd_set(1, 0, status); status[0] = 0; } else { switch (cpld_fail_state) { case CPLD_BLANK: osd_set_clear(1, 0, "CPLD is unprogrammed: Select correct CPLD"); break; case CPLD_UNKNOWN: osd_set_clear(1, 0, "CPLD is unknown: Select correct CPLD"); break; case CPLD_WRONG: osd_set_clear(1, 0, "Wrong CPLD (6bit CPLD on 3bit board)"); break; case CPLD_MANUAL: osd_set_clear(1, 0, "Manual CPLD recovery: Select correct CPLD"); break; case CPLD_UPDATE: osd_set_clear(1, 0, "Please update CPLD to latest version"); break; case CPLD_NOT_FITTED: osd_set_clear(1, 0, "CPLD Not Fitted"); break; } } int flags = 0; capinfo->ncapture = ncapture; log_info("Entering poll_keys_only, flags=%08x", flags); result = poll_keys_only(capinfo, flags); log_info("Leaving poll_keys_only, result=%04x", result); osd_set_clear(1, 0, " "); if (result & RET_EXPIRED) { ncapture = osd_key(OSD_EXPIRED); } else if (result & RET_SW1) { ncapture = osd_key(OSD_SW1); } else if (result & RET_SW2) { ncapture = osd_key(OSD_SW2); } else if (result & RET_SW3) { ncapture = osd_key(OSD_SW3); } } } powerup = 0; recalculate_hdmi_clock(HDMI_ORIGINAL, 0); capinfo->ncapture = POWERUP_MESSAGE_TIME / 10; osd_timer = POWERUP_MESSAGE_TIME; } if (osd_timer > 0 && osd_timer < POWERUP_MESSAGE_TIME) { if (parameters[F_POWERUP_MESSAGE]) { log_info("Display startup message"); int h_size = get_hdisplay(); int v_size = get_true_vdisplay(); if (sync_detected) { sprintf(osdline, "%d x %d @ %dHz", h_size, v_size, info_display_vsync_freq_hz); } else { sprintf(osdline, "%d x %d", h_size, v_size); } osd_set(0, ATTR_DOUBLE_SIZE, osdline); osd_display_interface(2); capinfo->ncapture = osd_timer; } else { osd_timer = 0; } } // Update capture info, in case sample width has changed // (this also re-selects the appropriate line capture) cpld->update_capture_info(capinfo); int flags = extra_flags() | clear; if (parameters[F_VSYNC_INDICATOR]) { flags |= BIT_VSYNC; } if (parameters[F_DEBUG]) { flags |= BIT_DEBUG; } //paletteFlags |= BIT_MULTI_PALETTE; // test multi palette if (capinfo->mode7) { if (capinfo->video_type == VIDEO_TELETEXT) { flags |= parameters[F_MODE7_DEINTERLACE] << OFFSET_INTERLACE; } else { flags |= (parameters[F_MODE7_DEINTERLACE] & 1) << OFFSET_INTERLACE; } } else { flags |= parameters[F_NORMAL_DEINTERLACE] << OFFSET_INTERLACE; } #ifdef MULTI_BUFFER if ((capinfo->video_type == VIDEO_PROGRESSIVE || (capinfo->video_type == VIDEO_INTERLACED && !interlaced)) && osd_active() && (parameters[F_NUM_BUFFERS] == 0)) { flags |= 2 << OFFSET_NBUFFERS; } else { flags |= parameters[F_NUM_BUFFERS] << OFFSET_NBUFFERS; } //log_info("Buffers = %d", (flags & MASK_NBUFFERS) >> OFFSET_NBUFFERS); #endif if (!osd_active() && reboot_required) { if (resolution_warning != 0) { osd_set_noupdate(0, 0, "If there is no display with new setting:"); osd_set_noupdate(1, 0, "Hold menu button during reset until you"); osd_set_noupdate(2, 0, "see the 50Hz disabled recovery message"); for (int i = 5; i > 0; i--) { sprintf(osdline, "Rebooting in %d secs ", i); log_info(osdline); osd_set_clear(4, 0, osdline); delay_in_arm_cycles_cpu_adjust(1000000000); } } else { // Wait a while to allow UART time to empty delay_in_arm_cycles_cpu_adjust(200000000); } reboot(); } if (osd_active()) { if (helper_flag != 0) { sprintf(osdline, "%d:%d %dHz %dPPM %d %s %dHz", get_haspect(), get_vaspect(), adjusted_clock, clock_error_ppm, lines_per_vsync, sync_names[capinfo->detected_sync_type & SYNC_BIT_MASK], source_vsync_freq_hz); osd_set(1, 0, osdline); helper_flag--; } else { if (status[0] != 0) { osd_set(1, 0, status); status[0] = 0; } else { if (!reboot_required) { if (sync_detected) { if (vlock_limited && (parameters[F_GENLOCK_MODE] != HDMI_ORIGINAL)) { if (force_genlock_range == GENLOCK_RANGE_INHIBIT) { sprintf(osdline, "Recovery mode: 50Hz disabled until reset"); } else { sprintf(osdline, "Genlock inhibited: Src=%dHz, Disp=%dHz", source_vsync_freq_hz, info_display_vsync_freq_hz); } osd_set(1, 0, osdline); } else { if (half_frame_rate != 0) { sprintf(osdline, "Warning: Monitor refresh = %dHz", info_display_vsync_freq_hz); osd_set(1, 0, osdline); } else { #ifdef USE_ARM_CAPTURE if ((_get_hardware_id() == _RPI2 || _get_hardware_id() == _RPI3) && capinfo->sample_width >= SAMPLE_WIDTH_9LO) { osd_set(1, 0, "Use GPU capture version for 9/12BPP"); } else { osd_set(1, 0, ""); } #else osd_set(1, 0, ""); #endif } } } else { osd_set(1, 0, "No sync detected"); } } else { osd_set(1, 0, "New setting requires reboot on menu exit"); } } } } capinfo->intensity = parameters[F_SCANLINE_LEVEL]; int old_palette_control = capinfo->palette_control; int old_flags = flags; if (get_parameter(F_DROP_FRAME) != 0 || half_frame_rate) { //half frame rate display detected (4K @ 25Hz / 30Hz) if (capinfo->vsync_type != VSYNC_NONINTERLACED_DEJITTER) { //inhibit alternate frame dropping when using the vertical dejitter mode as that stops it working //if ((flags & BIT_OSD) == 0) { // capinfo->palette_control |= INHIBIT_PALETTE_DIMMING_16_BIT; //if OSD not enabled then stop screen dimming when blank OSD turned on below flags |= BIT_SKIP_ALT_FRAME; //force dropping of alternate frames to eliminate tear //} } wait_for_pi_fieldsync(); //ensure that the source and Pi frames are in a repeatable phase relationship wait_for_source_fieldsync(); } log_debug("Entering rgb_to_fb, flags=%08x", flags); result = rgb_to_fb(capinfo, flags); log_debug("Leaving rgb_to_fb, result=%04x", result); capinfo->palette_control = old_palette_control; flags = old_flags; if (result & RET_SYNC_TIMING_CHANGED) { log_info("Timing exceeds window: H=%d, V=%d, Lines=%d, VSync=%d", (int)((double)total_hsync_period * 1000 / cpuspeed / (capinfo->nlines - 1)), (int)((double)vsync_period * 1000 / cpuspeed), (int) (((double)vsync_period/(total_hsync_period/(capinfo->nlines-1))) + 0.5), (result & RET_SYNC_POLARITY_CHANGED) ? 1 : 0); } if (result & RET_SYNC_STATE_CHANGED) { log_info("Sync state changed: Set=%d", result & RET_MODESET); } clear = 0; // capinfo->width = capinfo->width - config_overscan_left - config_overscan_right; // capinfo->height = capinfo->height - config_overscan_top - config_overscan_bottom; // Possibly the size or offset has been adjusted, so update current capinfo memcpy(&last_capinfo, capinfo, sizeof last_capinfo); memcpy(&last_clkinfo, &clkinfo, sizeof last_clkinfo); // capinfo->width = capinfo->width + config_overscan_left + config_overscan_right; // capinfo->height = capinfo->height + config_overscan_top + config_overscan_bottom; if (result & RET_EXPIRED) { ncapture = osd_key(OSD_EXPIRED); } else if (result & RET_SW1) { osd_timer = 0; ncapture = osd_key(OSD_SW1); } else if (result & RET_SW2) { osd_timer = 0; ncapture = osd_key(OSD_SW2); } else if (result & RET_SW3) { osd_timer = 0; ncapture = osd_key(OSD_SW3); } cpld->update_capture_info(capinfo); geometry_get_fb_params(capinfo); capinfo->palette_control = parameters[F_PALETTE_CONTROL]; if ((capinfo->palette_control == PALETTECONTROL_NTSCARTIFACT_CGA && parameters[F_NTSC_COLOUR] == 0)) { capinfo->palette_control = PALETTECONTROL_OFF; } capinfo->palette_control |= (get_inhibit_palette_dimming16() << 31); // fb_size_changed = ((capinfo->width - config_overscan_left - config_overscan_right) != last_capinfo.width) || ((capinfo->height - config_overscan_top - config_overscan_bottom) != last_capinfo.height) || (capinfo->bpp != last_capinfo.bpp) || (capinfo->sample_width != last_capinfo.sample_width || last_gscaling != gscaling); fb_size_changed = (capinfo->width != last_capinfo.width) || (capinfo->height != last_capinfo.height) || (capinfo->bpp != last_capinfo.bpp) || (capinfo->sample_width != last_capinfo.sample_width || last_gscaling != gscaling); if (result & RET_INTERLACE_CHANGED) { log_info("Interlace changed, HT = %d", hsync_threshold); if(capinfo->video_type == VIDEO_INTERLACED) { fb_size_changed |= 1; } } active_size_changed = (capinfo->chars_per_line != last_capinfo.chars_per_line) || (capinfo->nlines != last_capinfo.nlines); geometry_get_clk_params(&clkinfo); clk_changed = (clkinfo.clock != last_clkinfo.clock) || (clkinfo.line_len != last_clkinfo.line_len || (clkinfo.clock_ppm != last_clkinfo.clock_ppm)); last_modeset = modeset; if (parameters[F_AUTO_SWITCH] == AUTOSWITCH_MODE7 || parameters[F_AUTO_SWITCH] == AUTOSWITCH_VSYNC || parameters[F_AUTO_SWITCH] == AUTOSWITCH_IIGS) { if (result & RET_MODESET) { modeset = MODE_SET2; } else { modeset = MODE_SET1; } } else if (parameters[F_AUTO_SWITCH] == AUTOSWITCH_MANUAL || parameters[F_AUTO_SWITCH] == AUTOSWITCH_IIGS_MANUAL) { modeset = parameters[F_TIMING_SET]; } else { modeset = MODE_SET1; } mode_changed = modeset != last_modeset || capinfo->vsync_type != last_capinfo.vsync_type || capinfo->sync_type != last_capinfo.sync_type || capinfo->border != last_capinfo.border || capinfo->video_type != last_capinfo.video_type || capinfo->px_sampling != last_capinfo.px_sampling || cpld->get_sync_edge() != last_sync_edge || parameters[F_PROFILE] != last_profile || parameters[F_SAVED_CONFIG] != last_saved_config_number || last_subprofile != parameters[F_SUB_PROFILE] || cpld->get_divider() != last_divider || (result & (RET_SYNC_TIMING_CHANGED | RET_SYNC_STATE_CHANGED)); if (active_size_changed || fb_size_changed) { clear = BIT_CLEAR; } if ((clk_changed || (result & RET_INTERLACE_CHANGED)) && !fb_size_changed && !mode_changed) { target_difference = 0; resync_count = 0; // Measure the frame time and set the sampling clock calibrate_sampling_clock(0); // Force recalculation of the HDMI clock (if the genlock_mode property requires this) recalculate_hdmi_clock_line_locked_update(GENLOCK_FORCE); } } while (!mode_changed && !fb_size_changed && !restart_profile); log_info("Mode changed=%d, ret=%x, fb_size_changed=%d, restart_profile=%d, HsyncT=%d", mode_changed, (result & (RET_SYNC_TIMING_CHANGED | RET_SYNC_STATE_CHANGED)), fb_size_changed, restart_profile, hsync_threshold); osd_clear(); clear_full_screen(); } } void force_reinit() { restart_profile = 1; set_status_message("Configuration restored"); } int show_detected_status(int line) { char message[80]; int adjusted_width = capinfo->width; int pitch = capinfo->pitch; switch(capinfo->bpp) { case 4: pitch <<= 1; break; case 8: break; case 16: pitch >>= 1; break; } sprintf(message, " Pixel clock: %d Hz", adjusted_clock); osd_set(line++, 0, message); sprintf(message, " CPLD clock: %d Hz (x%d)", adjusted_clock * cpld->get_divider(), cpld->get_divider()); osd_set(line++, 0, message); if ((one_line_time_ns < (LINE_TIMEOUT - 1000)) && sync_detected) { sprintf(message, " Clock error: %d PPM", clock_error_ppm); osd_set(line++, 0, message); sprintf(message, " Line duration: %d ns", one_line_time_ns); osd_set(line++, 0, message); if (interlaced) { sprintf(message, "Lines per frame: %d (Interlaced %d)", lines_per_vsync, lines_per_2_vsyncs); } else { sprintf(message, "Lines per frame: %d", lines_per_vsync); osd_set(line++, 0, message); } } else { sprintf(message, " Clock error: No H sync detected"); osd_set(line++, 0, message); sprintf(message, " Line duration: No H sync detected"); osd_set(line++, 0, message); sprintf(message, "Lines per frame: No H sync detected"); osd_set(line++, 0, message); } if (sync_detected) { sprintf(message, " Frame rate: %d Hz (%.2f Hz)", source_vsync_freq_hz, source_vsync_freq); osd_set(line++, 0, message); sprintf(message, " Sync type: %s", sync_names_long[capinfo->detected_sync_type & SYNC_BIT_MASK]); osd_set(line++, 0, message); } else { sprintf(message, " Frame rate: No sync detected"); osd_set(line++, 0, message); sprintf(message, " Sync type: No sync detected"); osd_set(line++, 0, message); } sprintf(message, " Pixel Aspect: %d:%d", get_haspect(), get_vaspect()); osd_set(line++, 0, message); int double_width = (capinfo->sizex2 & SIZEX2_DOUBLE_WIDTH) >> 1; int double_height = capinfo->sizex2 & SIZEX2_DOUBLE_HEIGHT; sprintf(message, " Capture Size: %d x %d (%d x %d)", capinfo->chars_per_line << (3 - double_width), capinfo->nlines, capinfo->chars_per_line << 3, capinfo->nlines << double_height ); osd_set(line++, 0, message); sprintf(message, " H & V range: %d-%d x %d-%d", capinfo->h_offset, capinfo->h_offset + (capinfo->chars_per_line << (3 - double_width)) - 1, capinfo->v_offset, capinfo->v_offset + capinfo->nlines - 1); osd_set(line++, 0, message); sprintf(message, " Frame Buffer: %d x %d (%d x %d)", adjusted_width, capinfo->height, pitch, capinfo->height); osd_set(line++, 0, message); sprintf(message, " FB Bit Depth: %d", capinfo->bpp); osd_set(line++, 0, message); int h_size = get_hdisplay() - config_overscan_left - config_overscan_right; int v_size = get_vdisplay() - config_overscan_top - config_overscan_bottom; sprintf(message, " Pi Resolution: %d x %d (%d x %d)", get_hdisplay(), get_true_vdisplay(), h_size, v_size); osd_set(line++, 0, message); sprintf(message, " Pi Frame rate: %d Hz (%.2f Hz)", info_display_vsync_freq_hz, info_display_vsync_freq ); osd_set(line++, 0, message); sprintf(message, " Pi HDMI error: %d PPM", hdmi_error_ppm); osd_set(line++, 0, message); sprintf(message, " Pi Overscan: %d x %d (%d x %d)", h_overscan + config_overscan_left + config_overscan_right, v_overscan + config_overscan_top + config_overscan_bottom, adj_h_overscan + config_overscan_left + config_overscan_right, adj_v_overscan + config_overscan_top + config_overscan_bottom); osd_set(line++, 0, message); sprintf(message, " Scaling: %.2f x %.2f", ((double)(get_hdisplay() - h_overscan - config_overscan_left - config_overscan_right)) / capinfo->width,((double)(get_vdisplay() - v_overscan - config_overscan_top - config_overscan_bottom) / capinfo->height)); osd_set(line++, 0, message); return (line); } void kernel_main(unsigned int r0, unsigned int r1, unsigned int atags) { parameters[F_RESOLUTION] = -1; parameters[F_SCALING] = -1; parameters[F_AUTO_SWITCH] = 2; parameters[F_GENLOCK_LINE] = 10; parameters[F_GENLOCK_SPEED] = 2; parameters[F_MODE7_DEINTERLACE] = 6; parameters[F_PALETTE_CONTROL] = PALETTECONTROL_INBAND; parameters[F_BRIGHT] = 100; parameters[F_SAT] = 100; parameters[F_CONT] = 100; parameters[F_GAMMA] = 100; char message[128]; RPI_AuxMiniUartInit(115200, 8); rpi_mailbox_property_t *mp; unsigned int frame_buffer_start = 0; RPI_PropertyInit(); RPI_PropertyAddTag(TAG_ALLOCATE_BUFFER, 0x02000000); RPI_PropertyAddTag(TAG_SET_PHYSICAL_SIZE, 64, 64); RPI_PropertyAddTag(TAG_SET_VIRTUAL_SIZE, 64, 64); RPI_PropertyAddTag(TAG_SET_DEPTH, capinfo->bpp); RPI_PropertyProcess(); // FIXME: A small delay (like the log) is neccessary here // or the RPI_PropertyGet seems to return garbage int k = 0; for (int j = 0; j < 100000; j++) { k = k + j; } if ((mp = RPI_PropertyGet(TAG_ALLOCATE_BUFFER))) { frame_buffer_start = (unsigned int)mp->data.buffer_32[0]; } frame_buffer_start &= 0x3fffffff; #if defined(USE_CACHED_SCREEN) enable_MMU_and_IDCaches(frame_buffer_start + CACHED_SCREEN_OFFSET, CACHED_SCREEN_SIZE); #else enable_MMU_and_IDCaches(0,0); #endif //_enable_unaligned_access(); //do not use for an armv6 to armv8 compatible binary log_info("***********************RESET***********************"); log_info("RGB to HDMI booted"); #ifdef USE_ARM_CAPTURE sprintf(message, "Running ARM capture build. Kernel version: %s", GITVERSION); #else sprintf(message, "Running GPU capture build. Kernel version: %s", GITVERSION); #endif log_info(message); #if defined(USE_CACHED_SCREEN) log_info("Marked framebuffer from %08X to %08X as cached", frame_buffer_start + CACHED_SCREEN_OFFSET, frame_buffer_start + CACHED_SCREEN_OFFSET + CACHED_SCREEN_SIZE); #else log_info("No framebuffer area marked as cached"); #endif log_info("Pi Hardware detected as type %d", _get_hardware_id()); display_list = SCALER_DISPLAY_LIST; pi4_hdmi0_regs = PI4_HDMI0_PLL; gpioreg = (volatile uint32_t *)(_get_peripheral_base() + 0x101000UL); init_hardware(); if (_get_hardware_id() >= _RPI2) { int i; printf("main running on core %u\r\n", _get_core()); for (i = 0; i < 10000000; i++); #ifdef USE_MULTICORE #ifdef DONT_USE_MULTICORE_ON_PI2 if (_get_hardware_id() >= _RPI3 ) { #else if (_get_hardware_id() >= _RPI2 ) { #endif log_info("Starting core 1 at: %08X", _init_core); start_core(1, _init_core); } else { start_core(1, _spin_core); } #else start_core(1, _spin_core); #endif for (i = 0; i < 10000000; i++); start_core(2, _spin_core); for (i = 0; i < 10000000; i++); start_core(3, _spin_core); for (i = 0; i < 10000000; i++); } rgb_to_hdmi_main(); }