kopia lustrzana https://github.com/hoglet67/RGBtoHDMI
3854 wiersze
145 KiB
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();
|
|
}
|