/** * video_proc.c * * Video processing application, interaction with the VXR7200 chip. * * Copyright (c) 2022 Bigscreen, Inc. */ #include #include #include #include "oled_control.h" #include "vxr_interface.h" #include "prox_control.h" #include "power_control.h" TaskHandle_t xVideoProcTaskHandle = NULL; uint8_t vxr_firmware_version[VXR_FIRMWARE_LENGTH]; uint8_t pps_data[PPS_LENGTH]; bool tx0_status = false; bool tx1_status = false; bool video_enabled = false; // valid video status from VXR7200, or generating color bar pattern internally to displays bool displays_on = false; // set on or off by proximity sensor. when proximity is off, displays don't immediately turn off. instead // they're set to the minimum brightness for a short duration bool dsc_enabled = false; // true when receiving DSC encoded video stream uint8_t dp_link_rate = 0; // Retrieved from VXR7200. 0x06=RBR, 0x0A=HBR, 0x14=HBR2, 0x1E=HBR3 uint8_t dp_lane_count = 0; // Also retrieved from VXR7200 bool prox_timeout_expired = false; // after display_on false for duration of timer, goes true and fully turns off displays TimerHandle_t prox_timeout_timer; bool proxDisplaysOn = true; // set directly by proximity sensor. does not need video enabled to be set or cleared 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; bool VXR_Powered_Down = true; bool VXR_Startup_Succeeded = false; TimerHandle_t vxr_power_down_timer; static void track_uart_irq(uint32_t source_id, uint32_t source_mask); 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); } // EDID definitions are now moved to a separate file. #include "edid.h" const uint8_t* edid_in_use = (edid_with_color); #define EDID_SIZE (256) void video_copy_vxr_firmware(uint8_t* fwname) { memcpy(fwname, vxr_firmware_version, VXR_FIRMWARE_LENGTH); } /** * 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; } static BaseType_t VXR_Power_On() { RC_Error_T errval; VXR_Power_Control_On(); errval = VXR_Reset(); // if(RC_Success == errval) // errval = VXR_Start_Interface(); if(RC_Success == errval) errval = VXR_Get_Firmware_Name(vxr_firmware_version); else memset(vxr_firmware_version, '#', VXR_FIRMWARE_LENGTH); if(RC_Success == errval) 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 if(RC_Success == errval) errval = VXR_Disable_DSC(); } bool hpd_level; 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)); if(RC_Success == errval) { VXR_Startup_Succeeded = true; } VXR_Powered_Down = false; return (RC_Success == errval); } static BaseType_t VXR_Power_Off() { pio_disable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_HPD)); linkbox_hpd_set_inactive(); // disconnect DisplayPort VXR_Power_Control_Off(); VXR_Powered_Down = true; VXR_Startup_Succeeded = false; return pdPASS; } static BaseType_t VXR_Full_Reset_Sequence() { 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); } // temporarily disable DisplayPort HPD pio_disable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_HPD)); linkbox_hpd_set_inactive(); // disconnect DisplayPort return VXR_Power_On(); } static void vxr_power_timer_expired_callback( TimerHandle_t xTimer) { UNUSED(xTimer); // If no video received within 30 seconds of starting up the // VXR7200, we can power it back down if(!video_enabled) { VXR_Power_Off(); } // re-enable the TX UART pin change interrupt pio_enable_interrupt(TRACK_PIO, pio_get_pin_group_mask(PIN_TRACK_UART_TX)); } /*********************************************************************** * \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 bool eyetracking_error_flash = false; // enables display flashing as a warning for eyetracking LED overcurrent error bool eyetracking_error_flash_state = true; // true - displays on. false - displays off. uint32_t eyetracking_error_flash_count = 0; // for timing the flashes. counts times through the 20ms loop // bool need_to_reset_rx = false; // used when re-enabling OLED power and video was enabled UNUSED(pvParameters); // make sure to null terminate our firmware string memset(vxr_firmware_version, 0u, VXR_FIRMWARE_LENGTH); // create software timers 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 ); vxr_power_down_timer = xTimerCreate ("vxr_power_down_timer", // timer name pdMS_TO_TICKS(30*1000), // expires after 30 seconds pdFALSE, // does not auto-reload (void*)0, // ID field is not used vxr_power_timer_expired_callback); // 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); } // Check if VXR7200 sleep mode option is active. uint8_t vxr_sleep_enable = 0; sigv2_find_tag(SigTag_VXR_Sleep_Enable, &vxr_sleep_enable); // 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, ¬ificationValue, portMAX_DELAY); } VXR_Powered_Down = false; // 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(); // Save the firmware version if(RC_Success == errval) { errval = VXR_Get_Firmware_Name(vxr_firmware_version); } // 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 if(RC_Success == errval) { errval = load_edid(edid_in_use, EDID_SIZE); } if(RC_Success == errval) { VXR_Startup_Succeeded = true; } // Now let's wait on the OLED power rails while((notificationValue & VIDEOPROC_NOTIFY_1V8_AND_OLED_PWR_READY) == 0) { xTaskNotifyWait(0u, 0u, ¬ificationValue, 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; dsc_enabled = false; if(vxr_sleep_enable != 0) { // Start the power timer. If we don't get video in 30 seconds, shut down the VXR7200 // Enable interrupt for tracking uart tx xTimerStart(vxr_power_down_timer, 10); pio_handler_set_pin(PIN_TRACK_UART_TX, 0, track_uart_irq); // Don't enable the interrupt now. Enable it after the power down occurs due to the timer, // and then the UART can re-enable the VXR7200. } // 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, ¬ificationValue, 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; dsc_enabled = false; // Enable fan and LED xTaskNotify(get_fan_task_handle(), FAN_TASK_CODE_VIDEO_ON, 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(pdPASS == VXR_Full_Reset_Sequence()) send_code_to_hid_task(HID_REPORT_TASK_CODE_FOR_SUCCESS); else send_code_to_hid_task(HID_REPORT_TASK_CODE_FOR_ERROR); } if((notificationValue & VIDEOPROC_DISP_POWER_DISABLE) != 0) { // Turns off all power to the OLED panel so it can be hot swapped // Note that this will disable the 1.8V power rail, so anything using it // (proximity sensor) will also be disabled if(get_mainboardver() == board_ver_BS1) { Full_Panel_Shutdown(); send_code_to_hid_task(HID_REPORT_TASK_CODE_FOR_SUCCESS); } else { // BS2 doesn't have the ability to turn off 1.8V // so this command should always fail send_code_to_hid_task(HID_REPORT_TASK_CODE_FOR_ERROR); } } if((notificationValue & VIDEOPROC_DISP_POWER_ENABLE) != 0) { // Enables panel power again, then re-inits display panel register settings // if video had been enabled if(get_mainboardver() == board_ver_BS1) { ioport_set_pin_level(PIN_OLED_RESX, false); if(video_enabled) VXR_Reset_Rx(); vTaskDelay(200); Panel_Power_Control(true); vTaskDelay(200); ioport_set_pin_level(PIN_OLED_RESX, true); // issues a reset command to the VXR7200 RX status, but does not reset DP link training // causes reinitialization of TX, so MIPI can recover after panel power down /* if(video_enabled) { need_to_reset_rx = true; } */ // simply setting "video_enabled" to false will allow the normal startup // code (below) to reinitialize the panels video_enabled = false; dsc_enabled = false; send_code_to_hid_task(HID_REPORT_TASK_CODE_FOR_SUCCESS); } else { // BS2 doesn't have the ability to turn off 1.8V // so this command should always fail send_code_to_hid_task(HID_REPORT_TASK_CODE_FOR_ERROR); } } 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 recursions recursive_stack_overflow(64); } if((notificationValue & VIDEOPROC_EYETRACKING_ERROR) != 0) { eyetracking_error_flash = true; } if((notificationValue & VIDEOPROC_EYETRACKING_ERROR_CLEAR) != 0) { eyetracking_error_flash = false; } if((notificationValue & VIDEOPROC_TRACK_UART_ACTIVITY) != 0) { if(VXR_Powered_Down) { VXR_Power_On(); } if(vxr_sleep_enable != 0) { xTimerStart(vxr_power_down_timer, 10); // this will restart an already running timer, too } } if((notificationValue & VIDEOPROC_VXR_POWER_DOWN) != 0) { if(!VXR_Powered_Down) { VXR_Power_Off(); } } if((notificationValue & VIDEOPROC_VXR_POWER_UP) != 0) { if(VXR_Powered_Down) { VXR_Power_On(); } } // 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; dsc_enabled = false; displays_on = false; // Disable fan and led xTaskNotify(get_fan_task_handle(), FAN_TASK_CODE_VIDEO_OFF, 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); if(vxr_sleep_enable != 0) { // start the timer to disable VXR7200 xTimerStart(vxr_power_down_timer, 10); // this will restart an already running timer, too } } } // 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) && (!VXR_Powered_Down)) { // 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); VXR_Get_DP_Link_Rate_Lane_Count(&dp_link_rate, &dp_lane_count); 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(), FAN_TASK_CODE_VIDEO_ON, eSetValueWithOverwrite); xTaskNotify(get_led_task_handle(), LED_TASK_CODE_STATIC_COLOR, eSetBits); OLED_Startup(OLED_LEFT, vidsel); OLED_Startup(OLED_RIGHT, vidsel); // Vertical bars distortion fix OLED_Vert_Bars_Fix(OLED_LEFT); OLED_Vert_Bars_Fix(OLED_RIGHT); // 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); dsc_enabled = true; } else { dsc_enabled = false; } // Take the displays out of sleep vTaskDelay(OLED_POST_INIT_DELAY); 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(); } // when re-enabling out of OLED power down, reset the VXR7200 RX /* if(need_to_reset_rx) { need_to_reset_rx = false; VXR_Reset_Rx(); } */ } else { // video mode was lost after the delay video_enabled = false; dsc_enabled = false; } } // (tx0_status && tx1_status) } // (!video_enabled) } // left and right reset status // else { // video_enabled = false; // dsc_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 // bugfix Nov22, 2024 - move xTimerStop here instead of in the following if() block // when proximity timer is running and VXR reset happens, and then debug prox disable is issued // right after (while VXR reset is still in the middle of processing) then video_enabled will be // false by the time we get to this code. Left and right display resets will be asserted by the // freshly awoken VXR7200. // Then the timer will not be stopped. xTimerStop(prox_timeout_timer, 10); 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(); prox_timeout_expired = false; } } } // Eyetracking error: // causes displays to flash at a regular rate (500ms) as a warning to the user that they // should immediately take off the HMD if(eyetracking_error_flash) { if(eyetracking_error_flash_count == 0) { eyetracking_error_flash_state = !eyetracking_error_flash_state; OLED_invert_mode(OLED_LEFT, eyetracking_error_flash_state); OLED_invert_mode(OLED_RIGHT, eyetracking_error_flash_state); eyetracking_error_flash_count = (500u / VIDEO_TASK_DELAY); } else { eyetracking_error_flash_count -= 1u; } } else { if(false == eyetracking_error_flash_state) { // if we were left in an inverted state eyetracking_error_flash_state = true; OLED_invert_mode(OLED_LEFT, eyetracking_error_flash_state); OLED_invert_mode(OLED_RIGHT, eyetracking_error_flash_state); } } } } 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; } // Returns a bit-packed uint16 representing display parameters, // including on/off state, proximity dimming, video resolution, etc // Bit0 = Displays on or off // Bit1 = Proximity on or off (proximity triggered by presence) // Bit2 = Proximity timer expired (usually means displays fully off, rather than just super dim) // Bit3 = DSC enabled // Bit4-7: Video format (see Video_Selection_T for values) // Bit8-11: DisplayPort link rate. 1 = RBR, 2 = HBR, 3 = HBR2, 4 = HBR3 // Bit12-15: DisplayPort lane count. 1, 2, or 4 uint16_t video_get_display_status(void) { uint16_t retval = 0u; if(displays_on) retval |= VIDSTATUS_DISPLAYS_ON; if(proxDisplaysOn) retval |= VIDSTATUS_PROX_ON; if(prox_timeout_expired) retval |= VIDSTATUS_PROX_TIMER_EXPIRED; if(dsc_enabled) retval |= VIDSTATUS_DSC_ENABLED; if(video_enabled) { uint8_t vid_fmt_byte = check_format(&vid_fmt); retval |= (vid_fmt_byte << VIDSTATUS_VIDEO_MODE_SHIFT); uint8_t status_link_rate; switch(dp_link_rate){ case 0x06: status_link_rate = 1; break; case 0x0A: status_link_rate = 2; break; case 0x14: status_link_rate = 3; break; case 0x1E: status_link_rate = 4; break; default: status_link_rate = 0; break; } retval |= (0x0Fu & status_link_rate) << VIDSTATUS_LINK_RATE_SHIFT; retval |= (0x0Fu & dp_lane_count) << VIDSTATUS_LANE_COUNT_SHIFT; } return retval; } // Returns a bit-packed byte with VXR7200 status bits // Bit0 = VXR powered down // Bit1 = VXR startup successful uint8_t video_get_vxr_status(void) { uint8_t retval = 0; if(VXR_Powered_Down) retval |= (1 << 0); if(VXR_Startup_Succeeded) retval |= (1 << 1); return retval; } bool proximity_disable(void) { debug_prox_enabled = false; return true; } bool proximity_enable(void) { debug_prox_enabled = true; return true; } // sends a notification to turn on VXR7200 and set a timer waiting for video signals to arrive // turns off the interrupt when it occurs to prevent high processor usage in this // IRQ when UART is blastin' // interrupt re-enabled after timer expires static void track_uart_irq(uint32_t source_id, uint32_t source_mask) { (void)source_id; (void)source_mask; pio_disable_interrupt(TRACK_PIO, pio_get_pin_group_mask(PIN_TRACK_UART_TX)); BaseType_t xHigherPriorityTaskWoken = pdFALSE; xTaskNotifyFromISR(video_proc_task_handle(), VIDEOPROC_TRACK_UART_ACTIVITY, eSetBits, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }