/**
 * video_proc.c
 *
 * Video processing application, interaction with the VXR7200 chip.
 * 
 * Copyright (c) 2022 Bigscreen, Inc.
 */

#include <asf.h>
#include <stdbool.h>
#include "oled_control.h"
#include "vxr_interface.h"
#include "prox_control.h"
#include "power_control.h"

TaskHandle_t xVideoProcTaskHandle = NULL;

uint8_t pps_data[PPS_LENGTH];

bool tx0_status = false;
bool tx1_status = false;

bool video_enabled = false;
bool displays_on = false;
bool prox_timeout_expired = false;
TimerHandle_t prox_timeout_timer;

bool proxDisplaysOn = true;

bool debug_prox_enabled = true; // Can be disabled by USB HID command for temporarily bypassing the proximity

bool pulse_emission = false; // Used for 30Hz mode to prevent visible flickering

Video_Format_T vid_fmt;

uint16_t oled_current_brightness = DEFAULT_OLED_BRIGHTNESS;

static void recursive_stack_overflow(uint32_t repetitions) {
	if(repetitions == 0) return;
	
	//uint8_t my_useless_array[16];
	//my_useless_array[9] = repetitions & 0xFF;
	
	vTaskDelay(1); // make sure to check stack overflow each time through
	recursive_stack_overflow(repetitions-1);
}


/*** Not using this EDID. It's what should already exist in the VXR config. ***
const uint8_t edid_without_did20[256] = {
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x09, 0x27, 0x34, 0x12, 0xd2, 0x04, 0x00, 0x00,
	0x02, 0x1e, 0x01, 0x04, 0xa5, 0x00, 0x00, 0x78, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
	0x00, 0x42, 0x65, 0x79, 0x6f, 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0d,
	0x70, 0x12, 0x79, 0x00, 0x00, 0x03, 0x00, 0x28, 0x6c, 0x88, 0x01, 0x80, 0xdf, 0x13, 0x7f, 0x00,
	0x3f, 0x80, 0x1f, 0x00, 0xef, 0x09, 0x17, 0x00, 0x0e, 0x80, 0x01, 0x00, 0x83, 0x18, 0x01, 0x00,
	0xff, 0x0e, 0xff, 0x00, 0x3f, 0x80, 0x1f, 0x00, 0x7f, 0x07, 0x1b, 0x00, 0x0e, 0x80, 0x01, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x90,
};
***/
const uint8_t edid_for_optical_fatp[256] = {
	// The DSC modes are removed (5088x2544@75Hz and 3840x1920@90Hz)
	// Two non-DSC mode is added (3840x1920@60Hz and 2560x2560@30Hz)
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x95, 0x50, 0xD2, 0x04, 0x00, 0x00, 
	0x02, 0x1E, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xCA, 0xB4, 0x00, 0x80, 0xF0, 0x80, 0x18, 0x70, 0x40, 0x20, 
	0x92, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1E, 0xA5, 0x51, 0x00, 0x80, 0xA0, 0x00, 0x20, 0xA0, 
	0x40, 0x20, 0x02, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x75, 

	// DID1.2 block just used for the 5120x2560 @30Hz timing. It's too big for the regular EDID block.
	0x70, 0x12, 0x79, 0x00, 0x00, 0x03, 0x00, 0x14, 0x68, 0x9F, 0x00, 0x80, 0xFF, 0x13, 0x7F, 0x00, 
	0x3F, 0x80, 0x1F, 0x00, 0xFF, 0x09, 0x1F, 0x00, 0x0F, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x90
};

const uint8_t edid_for_fatp_mode1[256] = {
	// Only has a single non-DSC mode:
	// 3840x1920@60Hz
	// Dropping the 30Hz non-DSC mode makes it easier for test stations to go straight into 60Hz
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x95, 0x50, 0xD2, 0x04, 0x00, 0x00,
	0x02, 0x1E, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xCA, 0xB4, 0x00, 0x80, 0xF0, 0x80, 0x18, 0x70, 0x40, 0x20,
	0x92, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1E, 0xA5, 0x51, 0x00, 0x80, 0xA0, 0x00, 0x20, 0xA0,
	0x40, 0x20, 0x02, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x75,
	
	// Blank DID 1.2 block just for consistency
	0x70, 0x12, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x90
};

const uint8_t edid_for_fatp_mode2[256] = {
	// This is a bit different than the other FATP modes.
	// Only one mode is given: 5088x2544@60Hz
	// It does require DSC, so we keep the original EDID VID/PID, which tells
	// Nvidia and AMD drivers to still apply all of the DSC encoder fixes we require
	
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0xFF, 0x20, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0x00, 0x78, 0x75, 0xAA, 0x55, 0x3B, 0xB2, 0x29,
	0x10, 0x50, 0x54, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x5B,
	// DisplayID 2.0 Block
	0x70, 0x20, 0x79, 0x07, 0x00,
	0x22, 0x09, 0x14, // Timing Type VII
	
	0x61, 0x43, 0x0C, 0x88, 0xDF, 0x13, 0x7F, 0x00, 0x3F, 0x80, // 5088x2544 60Hz (marked as preferred)
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00,


	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, 0x00, // DSC passthrough 8bpp (vendor defined data block with VESA vendor ID)
	
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x90,
};
/*
const uint8_t edid_with_did20[256] = {
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0x02, 0x1E, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0D,
	
	0x70, 0x20, 0x79, 0x00, 0x00, // DisplayID 2.0
	0x22, 0x09, 0x28, // Timing Type VII
	0x1E, 0xF5, 0x0A, 0x00,	0xFF, 0x0E, 0xFF, 0x00, 0x3F, 0x80, // 3840x1920 90Hz (1/2)
	0x1F, 0x00, 0x7F, 0x07, 0x1B, 0x00, 0x0E, 0x80, 0x01, 0x00, // 3840x1920 90Hz (2/2)
	0x39, 0x54, 0x0F, 0x80, 0xDF, 0x13, 0x7F, 0x00,	0x3F, 0x80, // 5088x2544 75Hz (1/2)
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00, // 5088x2544 75Hz (2/2)
	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, // DSC passthrough 8bpp (vendor defined data block with VESA vendor ID)
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x90,
};
*/
/*
const uint8_t edid_testing_90Hz[256] = {
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0x02, 0x1E, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0D,
	
	0x70, 0x20, 0x79, 0x07, 0x00,// DisplayID 2.0, VR HMD
	0x22, 0x09, 0x3C,// Timing Type VII, 3 video timings
	0x39, 0x54, 0x0F, 0x08, 0xDF, 0x13, 0x7F, 0x00, 0x3F, 0x80,// 5088x2544, 75Hz (pixel clock = 1004.601 MHz)
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00,
	0x1E, 0xF5, 0x0A, 0x88, 0xFF, 0x0E, 0xFF, 0x00, 0x3F, 0x80,// 3840x1920, 90Hz (pixel clock = 718.11 MHz)
	0x1F, 0x00, 0x7F, 0x07, 0x1B, 0x00, 0x0E, 0x80, 0x01, 0x00,
	0x41, 0xD8, 0x0F, 0x08, 0xDF, 0x13, 0x7F, 0x00, 0x3F, 0x80,// 5088x2184, 90Hz (pixel clock = 1038.401 MHz)
	0x1F, 0x00, 0x87, 0x08, 0x1B, 0x00, 0x0E, 0x80, 0x01, 0x00,
	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, 0x00,// DSC passthrough 8bpp (vendor defined data block with VESA vendor ID)
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x15, 0x90,
};
*/
const uint8_t full_edid_with_75_72_and_90[256] = {
	// EDID Block 0
	// No timing information
	// MFG = BIG, Prod = 1234, Serial = 1234
	// Manufacture = 2022
	// Gamma = 120, R = 0.665,0.335, G = 0.232,0.695, B = 0.161,0.065, W = 0.313,0.329
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0xFF, 0x20, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0x00, 0x78, 0x75, 0xAA, 0x55, 0x3B, 0xB2, 0x29,
	0x10, 0x50, 0x54, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x5B,
	
	0x70, 0x20, 0x79, 0x07, 0x00, // DisplayID header
	0x22, 0x09, 0x3C, // Timing structure (3 timings)

	0x39, 0x54, 0x0F, 0x88, 0xDF, 0x13, 0x7F, 0x00, 0x3F, 0x80, // H:5088, V:2544, 75Hz,
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00,

	0x41, 0xB7, 0x0E, 0x08, 0xDF, 0x13, 0x7F, 0x00, 0x3F, 0x80, // H:5088, V:2544, 72Hz,
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00,

	0x1E, 0xF5, 0x0A, 0x08, 0xFF, 0x0E, 0xFF, 0x00, 0x3F, 0x80, // H:3840, V:1920, 90Hz,
	0x1F, 0x00, 0x7F, 0x07, 0x1B, 0x00, 0x0E, 0x80, 0x01, 0x00,

	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x90
};

const uint8_t edid_with_color[256] = {
	// EDID Block 0
	// No timing information
	// MFG = BIG, Prod = 1234, Serial = 0
	// Manufacture = 2022
	// Gamma = 120, R = 0.665,0.335, G = 0.232,0.695, B = 0.161,0.065, W = 0.313,0.329
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0xFF, 0x20, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0x00, 0x78, 0x75, 0xAA, 0x55, 0x3B, 0xB2, 0x29,
	0x10, 0x50, 0x54, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x5B,
	// DisplayID 2.0 Block
	0x70, 0x20, 0x79, 0x07, 0x00, 
	0x22, 0x09, 0x28, // Timing Type VII
	0x39, 0x54, 0x0F, 0x88, 0xDF, 0x13, 0x7F, 0x00,	0x3F, 0x80, // 5088x2544 75Hz (marked as preferred)
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00,
	0x1E, 0xF5, 0x0A, 0x08,	0xFF, 0x0E, 0xFF, 0x00, 0x3F, 0x80, // 3840x1920 90Hz 
	0x1F, 0x00, 0x7F, 0x07, 0x1B, 0x00, 0x0E, 0x80, 0x01, 0x00,
	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, 0x00, // DSC passthrough 8bpp (vendor defined data block with VESA vendor ID)
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1, 0x90,
};

const uint8_t edid_with_90hz_only[256] = {
	// EDID Block 0
	// No timing information
	// MFG = BIG, Prod = 1234, Serial = 0
	// Manufacture = 2022
	// Gamma = 120, R = 0.665,0.335, G = 0.232,0.695, B = 0.161,0.065, W = 0.313,0.329
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0xFF, 0x20, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0x00, 0x78, 0x75, 0xAA, 0x55, 0x3B, 0xB2, 0x29,
	0x10, 0x50, 0x54, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x5B,
	// DisplayID 2.0 Block
	
	0x70, 0x20, 0x79, 0x07, 0x00,	// DisplayID 2.0 header
	0x22, 0x09, 0x14,		// Timing Type VII data block
	0x1E, 0xF5, 0x0A, 0x88, 0xFF, 0x0E, 0xFF, 0x00, 0x3F, 0x80, // 3840x1920 at 90Hz 
	0x1F, 0x00, 0x7F, 0x07, 0x1B, 0x00, 0x0E, 0x80, 0x01, 0x00, // 3840x1920 at 90Hz (cont'd)
	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, 0x00, // DSC passthrough 8bpp (vendor defined data block with VESA vendor ID) 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x90, 
};

const uint8_t edid_with_75hz_only[256] = {
	// EDID Block 0
	// No timing information
	// MFG = BIG, Prod = 1234, Serial = 0
	// Manufacture = 2022
	// Gamma = 120, R = 0.665,0.335, G = 0.232,0.695, B = 0.161,0.065, W = 0.313,0.329
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0xFF, 0x20, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0x00, 0x78, 0x75, 0xAA, 0x55, 0x3B, 0xB2, 0x29,
	0x10, 0x50, 0x54, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x5B,
	// DisplayID 2.0 Block
	0x70, 0x20, 0x79, 0x07, 0x00,	// DisplayID 2.0 header
	0x22, 0x09, 0x14,		// Timing Type VII data block
	0x39, 0x54, 0x0F, 0x88, 0xDF, 0x13, 0x7F, 0x00,	0x3F, 0x80, // 5088x2544 75Hz (marked as preferred)
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00, // 5088x2544 75Hz (cont'd)
	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, 0x00, // DSC passthrough 8bpp (vendor defined data block with VESA vendor ID)
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x90,
};

const uint8_t edid_with_72hz_only[256] = {
	// EDID Block 0
	// No timing information
	// MFG = BIG, Prod = 1234, Serial = 0
	// Manufacture = 2022
	// Gamma = 120, R = 0.665,0.335, G = 0.232,0.695, B = 0.161,0.065, W = 0.313,0.329
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0xFF, 0x20, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0x00, 0x78, 0x75, 0xAA, 0x55, 0x3B, 0xB2, 0x29,
	0x10, 0x50, 0x54, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x5B,
	// DisplayID 2.0 Block
	0x70, 0x20, 0x79, 0x07, 0x00, // DisplayID header
	0x22, 0x09, 0x14, // Timing structure (1 timings)

	0x41, 0xB7, 0x0E, 0x88, 0xDF, 0x13, 0x7F, 0x00, 0x3F, 0x80, // H:5088, V:2544, 72Hz,
	0x1F, 0x00, 0xEF, 0x09, 0x17, 0x00, 0x0E, 0x80, 0x01, 0x00,

	0x7E, 0x00, 0x07, 0x3A, 0x02, 0x92, 0x81, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xCA, 0x90
};

const uint8_t edid_with_did20_no_native[256] = {
	0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x09, 0x27, 0x34, 0x12, 0xD2, 0x04, 0x00, 0x00,
	0x02, 0x1E, 0x01, 0x04, 0xA5, 0x00, 0x00, 0x78, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
	0x00, 0x42, 0x65, 0x79, 0x6F, 0x6E, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0D,
	0x70, 0x20, 0x79, 0x00, 0x00, 0x22, 0x09, 0x14, 0x1E, 0xF5, 0x0A, 0x80, 0xFF, 0x0E, 0xFF, 0x00,
	0x3F, 0x80, 0x1F, 0x00, 0x7F, 0x07, 0x1B, 0x00, 0x0E, 0x80, 0x01, 0x00, 0x7E, 0x00, 0x07, 0x3A,
	0x02, 0x92, 0x81, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x90,
};

const uint8_t* edid_in_use	=			(edid_with_color);
#define EDID_SIZE				(256)

/** 
 * Private function that checks incoming video format and ensures it matches with 
 * known, supported resolutions.
 */
static Video_Selection_T check_format(Video_Format_T* fmt) {
	if((fmt->Hactive == 5088) && (fmt->Vactive == 2544)) {
		if((fmt->Frame_100 > 7400) && (fmt->Frame_100 < 7600)) {
			return Vid_H2544_V2544_75Hz;
		}
		if((fmt->Frame_100 > 7100) && (fmt->Frame_100 < 7300)) {
			return Vid_H2544_V2544_72Hz;
		}
		if((fmt->Frame_100 > 5900) && (fmt->Frame_100 < 6100)) {
			return Vid_H2544_V2544_60Hz;
		}
	}
	/*
	if((fmt->Hactive == 5088) && (fmt->Vactive == 2184)) {
		if((fmt->Frame_100 > 8900) && (fmt->Frame_100 < 9100)) {
			return Vid_H2544_V2184_90Hz;
		}
	}
	*/
	if((fmt->Hactive == 3840) && (fmt->Vactive == 1920)) {
		if((fmt->Frame_100 > 8900) && (fmt->Frame_100 < 9100)) {
			return Vid_H1920_V1920_90Hz;
		}
	}
	if((fmt->Hactive == 2560) && (fmt->Vactive == 2560)) {
		if((fmt->Frame_100 > 2900) && (fmt->Frame_100 < 3100)) {
			return Vid_H2560_V2560_30Hz;
		}
	}
	if((fmt->Hactive == 5120) && (fmt->Vactive == 2560)) {
		if((fmt->Frame_100 > 2900) && (fmt->Frame_100 < 3100)) {
			return Vid_H2560_V2560_30Hz;
		}
	}
	if((fmt->Hactive == 3840) && (fmt->Vactive == 1920)) {
		if((fmt->Frame_100 > 5900) && (fmt->Frame_100 < 6100)) {
			return Vid_H1920_V1920_60Hz;
		}
	}	
	
	return Vid_Unknown;
}

/* Video displays on / off prox
 * 
 * When proximity sensor detects the HMD is not on your
 * face anymore, the first action is to apply maximum
 * dimming to the displays. There's still a baaaarely 
 * visible image at this point, but it should significantly
 * reduce burnout. A 5 minute timer is started. If the HMD
 * is back on before the timeout, then dimming is simply
 * set back to normal. If the timer expires, then the 
 * displays are turned off. Then the next time the HMD is 
 * placed back on, the display are first turned back on, 
 * followed by setting dimming back to normal. Unfortunately,
 * the display-on command causes a temporary blue shift in 
 * the image, so we don't want to do it all the time.
 */
static void video_displays_off_prox(void) {
	// Simply set dimming to lowest value possible
	uint8_t retval;
	uint_fast8_t i;
	for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
		retval = OLED_set_brightness(OLED_LEFT, 0, pulse_emission);
		if(retval == pdPASS)
			break;
	}
	for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
		retval = OLED_set_brightness(OLED_RIGHT, 0, pulse_emission);
		if(retval == pdPASS)
			break;
	}
}

static void video_displays_on_prox(void) {
	// Check if "display off" command was sent due to timeout
	if(prox_timeout_expired) {
		// Turn on displays first
		OLED_display_on_off(OLED_LEFT, true);
		OLED_display_on_off(OLED_RIGHT, true);
		vTaskDelay(OLED_POST_INIT_DELAY);
		OLED_dimming_post_init(OLED_LEFT);
		OLED_dimming_post_init(OLED_RIGHT);
		vTaskDelay(OLED_POST_INIT_DELAY);
		prox_timeout_expired = false;
	}
	// Set dimming back to normal
	uint8_t retval;
	uint_fast8_t i;
	for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
		retval = OLED_set_brightness(OLED_LEFT, oled_current_brightness, pulse_emission);
		if(retval == pdPASS)
			break;
	}
	for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
		retval = OLED_set_brightness(OLED_RIGHT, oled_current_brightness, pulse_emission);
		if(retval == pdPASS)
			break;
	}
}

static void prox_timer_expired_callback( TimerHandle_t xTimer ) {
	UNUSED(xTimer);
	// Handles the 5 minute timer expiration.
	// Turns off the displays
	prox_timeout_expired = true;
	OLED_display_on_off(OLED_LEFT, false);
	OLED_display_on_off(OLED_RIGHT, false);
}

TaskHandle_t video_proc_task_handle(void) {
	return xVideoProcTaskHandle;
}


bool video_is_enabled(void) {
	return video_enabled;
}

void start_video_task(void) {
	if(xTaskCreate(task_video_proc, "video_proc", TASK_VIDEO_PROC_STACK_SIZE, NULL,
	TASK_VIDEO_PROC_PRIORITY, &xVideoProcTaskHandle) != pdPASS) {
		printf("Failed to create video processor task\r\n");
	}
}

static RC_Error_T load_edid(const uint8_t* buf, uint32_t len_bytes) {
	RC_Error_T errval = RC_Success;
	uint32_t tempreg;
	for(uint16_t i = 0; i < (len_bytes/sizeof(uint32_t)); i++) {
		tempreg = (((uint32_t)buf[i*sizeof(uint32_t) + 0]) << 0)
		+ (((uint32_t)buf[i*sizeof(uint32_t) + 1]) << 8)
		+ (((uint32_t)buf[i*sizeof(uint32_t) + 2]) << 16)
		+ (((uint32_t)buf[i*sizeof(uint32_t) + 3]) << 24);
		if(RC_Success == errval) {
			errval = VXR_Write_Register(EDID_RAM_ADDRESS + i*sizeof(uint32_t), tempreg);
		}
	}
	return errval;
}

/***********************************************************************
 * \brief This task performs video processing using the VXR7200
 */
void task_video_proc(void *pvParameters) {
	uint32_t notificationValue;
	RC_Error_T errval;
	
	bool prox_enabled = true;	// default to using the proximity sensor (unless it's unplugged)
								// can be overridden by user signature data
	
	bool debug_prox_just_disabled = false; // Set true when HID commands the prox should be disabled
	
	UNUSED(pvParameters);
	
	prox_timeout_timer = xTimerCreate
		("prox_timeout_timer", // timer name
		pdMS_TO_TICKS(5*60*1000), // expires after 5 minutes
		pdFALSE, // does not auto-reload
		(void*)0, // ID field is not used
		prox_timer_expired_callback );
	
	
	uint8_t startup_fan_speed = DEFAULT_FAN_SPEED;
	// Grab the startup fan speed. No need to check return value, find_tag
	// doesn't modify if the tag isn't found.
	sigv2_find_tag(SigTag_Fan_Speed, &startup_fan_speed);
	
	// Check if EDID override is active.
	uint8_t edid_override = 0;
	if(sigv2_find_tag(SigTag_EDID_Switch, &edid_override)) {
		video_proc_select_edid((Edid_Select_T)edid_override);
	}
	
	// xVideoProcTaskHandle = xTaskGetCurrentTaskHandle();
	
	// First - we wait until the VXR chip is ready to access over I2C.
	notificationValue = 0;
	while((notificationValue & VIDEOPROC_NOTIFY_1V0_READY) == 0) {
		xTaskNotifyWait(0u, 0u, &notificationValue, portMAX_DELAY);
	}
	
	// Now that 1.0V rail is ready, we can initialize the VXR chip
	// Set a >250ms delay before accessing the debug I2C port
	vTaskDelay(VXR_RC_INTERFACE_DELAY);
	
	// Try enabling the VXR remote control (RC) debug access port
	errval = VXR_Start_Interface();
	
	// Change the EDID in VXR RAM to DisplayID 2.0
	// The VXR firmware won't allow DID2.0 in storage, but we can overwrite it after boot
	errval = load_edid(edid_in_use, EDID_SIZE);


	// Now let's wait on the OLED power rails
	while((notificationValue & VIDEOPROC_NOTIFY_1V8_AND_OLED_PWR_READY) == 0) {
		xTaskNotifyWait(0u, 0u, &notificationValue, portMAX_DELAY);
	}
	
	vTaskDelay(OLED_RESET_DELAY_MS);
	ioport_set_pin_level(PIN_OLED_RESX, true);
	
	// Now we are ready to announce that a display is connected
	// Copy the current value of HPD from VXR7200 to output
	bool hpd_level = ioport_get_pin_level(PIN_DDIC_HPD);
	if(hpd_level) {
		// HPD is asserted, tell the linkbox!
		linkbox_hpd_set_active();
		} else {
		linkbox_hpd_set_inactive();
	}
	// Enable the interrupt so automatic HPD toggles will still be forwarded to the DisplayPort cable
	pio_enable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_HPD));
	
	video_enabled = false;
	
	// Disable proximity sensor if user settings tells us to do so
	if(sigv2_find_tag(SigTag_Prox_Disable, (uint8_t*)&prox_enabled)) {
		prox_enabled = !prox_enabled; // if prox_disabled = true, prox_enabled should now be false
	}
	
	while(true) {
		vTaskDelay(VIDEO_TASK_DELAY);
		// Check notification value, see if we need to do anything
		xTaskNotifyWait(0u, VIDEOPROC_NOTIFY_TASKBITS, &notificationValue, 0);
		if((notificationValue & VIDEOPROC_NOTIFY_DEBUG_PROX_DISABLED) != 0) {
			debug_prox_just_disabled = true;
		}
		if((notificationValue & VIDEOPROC_COLORBAR) != 0) {


			pio_disable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_HPD));
			linkbox_hpd_set_inactive(); // disconnect DisplayPort
			ioport_set_pin_level(PIN_DDIC_RST, false); // Active low, "false" asserts reset
			vTaskDelay(DDIC_RESET_DELAY_MS);
			Power_Safe_Shutdown();
			vTaskDelay(500);
			VXR_Power_Control_On(); // enables the VXR reset, but we want it off
			ioport_set_pin_level(PIN_DDIC_RST, false);
			Panel_Power_Control(true);
			vTaskDelay(OLED_RESET_DELAY_MS);
			ioport_set_pin_level(PIN_OLED_RESX, true);
			
			OLED_Colorbar_Test(OLED_LEFT);
			OLED_Colorbar_Test(OLED_RIGHT);
			OLED_sleep_in_out(OLED_LEFT, false);
			OLED_sleep_in_out(OLED_RIGHT, false);
			OLED_display_on_off(OLED_LEFT, true);
			OLED_display_on_off(OLED_RIGHT, true);
			vTaskDelay(OLED_POST_INIT_DELAY);
			OLED_dimming_post_init(OLED_LEFT);
			OLED_dimming_post_init(OLED_RIGHT);
			video_enabled = true;
			// Enable fan and LED
			xTaskNotify(get_fan_task_handle(), (startup_fan_speed | FAN_TASK_CODE_NEW_SPEED), eSetValueWithOverwrite);
			xTaskNotify(get_led_task_handle(), LED_TASK_CODE_STATIC_COLOR, eSetBits);
			// Wait forever - only way to get video back is to reset or power cycle
			vTaskDelay(portMAX_DELAY);
		}
		if((notificationValue & VIDEOPROC_NOTIFY_VXR_RESET) != 0) {
			// Reset the VXR chip, then re-enable I2C interface, reload the EDID
			// also reset the displays if they were running
			if(video_enabled) {
				video_enabled = false;
				ioport_set_pin_level(PIN_OLED_RESX, false);
				vTaskDelay(OLED_RESET_DELAY_MS);
				ioport_set_pin_level(PIN_OLED_RESX, true);
			}
			pio_disable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_HPD));
			linkbox_hpd_set_inactive(); // disconnect DisplayPort
			errval = VXR_Reset(); // this will wait the required delay before starting the I2C interface
			errval = VXR_Start_Interface();
			errval = load_edid(edid_in_use, EDID_SIZE);
			// Hacky workaround to disable DSC when in FATP mode
			// Mode 0 (60Hz 1920, 30Hz 2560) and Mode 1 (60Hz 1920 only) must both have DSC turned off
			// But FATP Mode 2 (60Hz 2544) should have DSC on, so it's not in this following "if" statement.
			if((edid_in_use == edid_for_optical_fatp) || (edid_in_use == edid_for_fatp_mode1)) {
				// Disable FATP by changing register on VXR7200
				errval = VXR_Disable_DSC();
			}
			
			hpd_level = ioport_get_pin_level(PIN_DDIC_HPD);
			if(hpd_level) {
				linkbox_hpd_set_active();
			} else {
				linkbox_hpd_set_inactive();
			}
			pio_enable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_HPD));
			send_code_to_hid_task(HID_REPORT_TASK_CODE_FOR_SUCCESS);
		}
		if((notificationValue & VIDEOPROC_STACK_OVERFLOW) != 0) {
			// Call a recursive function that will stack 2 words every time it is entered
			// the integer is the number of recurions
			recursive_stack_overflow(64);
		}
		
		// Check if we can disable the OLED panels (video was enabled, now it isn't)
		if(video_enabled) {
			if(!(main_left_display_reset_status()) || !(main_right_display_reset_status())) {
				video_enabled = false;
				displays_on = false;
				// Disable fan and led
				xTaskNotify(get_fan_task_handle(), (0 | FAN_TASK_CODE_NEW_SPEED), eSetValueWithOverwrite);
				xTaskNotify(get_led_task_handle(), LED_TASK_CODE_BREATHING, eSetBits);
				// toggle a reset if video is lost
				ioport_set_pin_level(PIN_OLED_RESX, false);
				vTaskDelay(OLED_RESET_DELAY_MS);
				ioport_set_pin_level(PIN_OLED_RESX, true);
			}
		}
	
		// Check if we should enable the OLED panels
		if(main_left_display_reset_status() && main_right_display_reset_status()) {
			// Both displays are out of reset
			if(!video_enabled) {
				// Check until video is enabled
				tx0_status = VXR_Get_TXn_Video_Status(0);
				tx1_status = VXR_Get_TXn_Video_Status(1);
				if(tx0_status && tx1_status) {
					video_enabled = true;
					// We can now take the displays out of sleep and turn them on
					// Determine what resolution and framerate we've got going on
					VXR_Get_Rx_Video_Format(&vid_fmt);
						
					Video_Selection_T vidsel = check_format(&vid_fmt);
					if(vidsel == Vid_H2560_V2560_30Hz || vidsel == Vid_H2544_V2544_60Hz) {
						pulse_emission = true;
					} else {
						pulse_emission = false;
					}
						
					// So...it seems like the VXR7200 will just send out the sleep-out
					// and display-on commands at some point after MIPI TX have entered video mode
					// Best way to avoid spurious screens on is to insert a delay, then perform the
					// OLED_Startup routines which should include sleep-in and display-off
						
					vTaskDelay(PANEL_INIT_TO_STARTUP_DELAY);
					
					// Check again if the video mode is still active
					tx0_status = VXR_Get_TXn_Video_Status(0);
					tx1_status = VXR_Get_TXn_Video_Status(1);
					if(tx0_status && tx1_status) {
						
						// Enable the Fan and RGB LED to show that video is coming in
						// Ramp fan speed up to final value
						// Ramp speed is set to 1% per 10ms.
						// Could take up to 1 sec if startup is set to 100%
						xTaskNotify(get_fan_task_handle(), (startup_fan_speed | FAN_TASK_CODE_NEW_SPEED), eSetValueWithOverwrite);
						xTaskNotify(get_led_task_handle(), LED_TASK_CODE_STATIC_COLOR, eSetBits);
							
						OLED_Startup(OLED_LEFT, vidsel);
						OLED_Startup(OLED_RIGHT, vidsel);
						
						// Check if we have a default brightness set, and if so change to it
						if(sigv2_tag_length(SigTag_Display_Brightness) > 0) {
							
							sigv2_find_tag(SigTag_Display_Brightness, (uint8_t*)&(oled_current_brightness));
							// OLED_set_brightness(OLED_LEFT, oled_current_brightness, pulse_emission);
							// OLED_set_brightness(OLED_RIGHT, oled_current_brightness, pulse_emission);
						}

						// Check if we are receiving DSC encoded video
						errval = VXR_Get_PPS_TXn(pps_data, 0);
						// We can just check the first display, since TX1 will have the
						// same data as TX0.
						// If it's DSC video, then the first 4 PPS bytes will be 0x11, 0x00, 0x00, 0x89
						if((pps_data[0] == 0x11) && (pps_data[1] == 0x00)
							&& (pps_data[2] == 0x00) && (pps_data[3] == 0x89)) {
							// Update the displays PPS settings
							OLED_set_pps(OLED_LEFT, pps_data);
							OLED_set_pps(OLED_RIGHT, pps_data);
						}
						// Take the displays out of sleep
						OLED_sleep_in_out(OLED_LEFT, false);
						OLED_sleep_in_out(OLED_RIGHT, false);
						
						// Minimum brightness
						uint_fast8_t i;
						uint8_t retval;
						for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
							retval = OLED_set_brightness(OLED_LEFT, 0, pulse_emission);
							if(retval == pdPASS)
								break;
						}
						for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
							retval = OLED_set_brightness(OLED_RIGHT, 0, pulse_emission);
							if(retval == pdPASS)
								break;
						}
						
						// Turn on displays
						OLED_display_on_off(OLED_LEFT, true);
						OLED_display_on_off(OLED_RIGHT, true);
						vTaskDelay(OLED_POST_INIT_DELAY);
						OLED_dimming_post_init(OLED_LEFT);
						OLED_dimming_post_init(OLED_RIGHT);
						
						// If prox is detected, turn displays on
						if(prox_enabled && prox_is_connected() && debug_prox_enabled) {
							if(proxDisplaysOn) {
								displays_on = true;
								video_displays_on_prox();
							} else {
								displays_on = false;
								video_displays_off_prox();
								xTimerStart(prox_timeout_timer, 10);
							}
						} else {
							// No proximity sensor, or it's manually disabled, turn on displays regardless
							displays_on = true;
							video_displays_on_prox();
						}
					} else { // video mode was lost after the delay
						video_enabled = false;
					}
				} // (tx0_status && tx1_status)
			} // (!video_enabled)
		} // left and right reset status
		else {
			video_enabled = false;
		}
		
		// Check proximity sensor - need to turn on or off displays?
		if(prox_enabled && debug_prox_enabled) {
			if(proxDisplaysOn) {
				if(false == prox_hmd_on_person()) {
					// distance sensor below threshold, now turn off the displays
					proxDisplaysOn = false;
					if(video_is_enabled()) {
						displays_on = false;
						video_displays_off_prox();
						xTimerStart(prox_timeout_timer, 10);
					}
				}
				} else {
				if(true == prox_hmd_on_person()) {
					// got close, now turn them on
					proxDisplaysOn = true;
					if(video_is_enabled()) {
						displays_on = true;
						video_displays_on_prox();
						xTimerStop(prox_timeout_timer, 10);
						prox_timeout_expired = false;
					}
				}
			}
		}
		
		// Special case - video was on, prox had displays disabled, but the HID disable prox was just sent
		// we need to enable displays
		if(debug_prox_just_disabled) {
			debug_prox_just_disabled = false; // don't get stuck in this loop
			if(false == proxDisplaysOn) {
				if(video_enabled) {
					// turn on the displays now. the proximity sensor won't be used anymore and can't trigger them.
					displays_on = true;
					video_displays_on_prox();
					xTimerStop(prox_timeout_timer, 10);
					prox_timeout_expired = false;
				}
			}
		}
	}
}

bool displays_are_on(void) {
	return displays_on;
}

void video_proc_set_fatp_edid(FATP_Mode_T fatp_mode_sel) {
	// overwrites the edid pointer to the special FATP edid
	switch(fatp_mode_sel) {

		case FATP_Mode_0:
			// both 60Hz and 30Hz, non-DSC
			edid_in_use = edid_for_optical_fatp;
			break;
		case FATP_Mode_1:
			edid_in_use = edid_for_fatp_mode1;
			break;
		case FATP_Mode_2:
			edid_in_use = edid_for_fatp_mode2;
			break;
		case FATP_Mode_None:
			// leave FATP, back to normal
			edid_in_use = edid_with_color;
			break;
		default:
			// upon any unknown value, return to regular EDID
			edid_in_use = edid_with_color;
			break;
	}

}

uint8_t video_proc_select_edid(Edid_Select_T edid_select) {
	uint8_t retval = pdFAIL;
	switch(edid_select) {
		case EDID_DEFAULT:
			edid_in_use = edid_with_color;
			retval = pdPASS;
		break;
		case EDID_90HZ_ONLY:
			edid_in_use = edid_with_90hz_only;
			retval = pdPASS;
		break;
		case EDID_75HZ_ONLY:
			edid_in_use = edid_with_75hz_only;
			retval = pdPASS;
		break;
		case EDID_72HZ_ONLY:
			edid_in_use = edid_with_72hz_only;
			retval = pdPASS;
		break;
		default:
		// do nothing, retval remains at fail
		break;
	}
	
	return retval;
}

uint8_t video_set_brightness(uint16_t new_brightness) {
	oled_current_brightness = new_brightness;
	// Only change the panel brightness right now if allowed by 
	// proximity sensor
	if(displays_on) {

		uint8_t retval;
		uint_fast8_t i;
		for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
			retval = OLED_set_brightness(OLED_LEFT, new_brightness, pulse_emission);
			if(retval == pdPASS)
			break;
		}
		for(i = 0; i < NUM_BRIGHTNESS_RETRIES; i++) {
			retval = OLED_set_brightness(OLED_RIGHT, new_brightness, pulse_emission);
			if(retval == pdPASS)
			break;
		}
		return retval;
	}
	return pdPASS;
}

uint8_t video_set_colorbar(void) {
	// Shows a colorbar pattern on the OLED panels
	// Disables the VXR and disconnects displayport first
	xTaskNotify(video_proc_task_handle(), VIDEOPROC_COLORBAR, eSetValueWithOverwrite);
	return pdPASS;
}

uint16_t video_get_brightness(void) {
	return oled_current_brightness;
}

bool proximity_disable(void) {
	debug_prox_enabled = false;
	return true;
}

bool proximity_enable(void) {
	debug_prox_enabled = true;
	return true;
}


