RGBtoHDMI/src/rgb_to_hdmi.c

3854 wiersze
145 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <stdint.h>
#include <limits.h>
#include <math.h>
#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();
}