/** * vxr_interface.c * * Low-level commands for the VXR7200 Remote Control (RC) interface over I2C. * * Copyright (c) 2022 Bigscreen, Inc. */ #include #include #include #include "main.h" #include "i2c.h" #include "rtt_util.h" #include "vxr_interface.h" bool vxr_interface_started = false; static uint8_t rc_bus_write(uint32_t addr, uint32_t data) { uint8_t i2c_write_data[8]; I2C_Transaction_Request i2ctxn = { .cb = NULL, .dev_addr = VXR_ADDR, .shift = I2C_Unshifted, .len = 8, // 32-bit address, 32-bit data .internal_addr = 0, // not used .data = i2c_write_data, .ttype = I2C_SIMPLE_WRITE }; // Address and data are least-significant-byte first i2c_write_data[0] = (uint8_t)((addr & 0x000000FF) >> 0); i2c_write_data[1] = (uint8_t)((addr & 0x0000FF00) >> 8); i2c_write_data[2] = (uint8_t)((addr & 0x00FF0000) >> 16); i2c_write_data[3] = (uint8_t)((addr & 0xFF000000) >> 24); i2c_write_data[4] = (uint8_t)((data & 0x000000FF) >> 0); i2c_write_data[5] = (uint8_t)((data & 0x0000FF00) >> 8); i2c_write_data[6] = (uint8_t)((data & 0x00FF0000) >> 16); i2c_write_data[7] = (uint8_t)((data & 0xFF000000) >> 24); i2c_lock(DDIC_I2C()); uint8_t retval = i2c_transact_blocking(DDIC_I2C(), &i2ctxn, DEFAULT_I2C_WAIT_TIME); i2c_unlock(DDIC_I2C()); return retval; } static uint8_t rc_bus_read(uint32_t addr, uint32_t* data) { uint8_t retval; uint8_t i2c_read_data[4]; I2C_Transaction_Request i2ctxn = { .cb = NULL, .dev_addr = VXR_ADDR, .shift = I2C_Unshifted, .len = 4, // 32-bit address or 32-bit data, two transactions to do the read .internal_addr = 0, // not used .data = i2c_read_data, .ttype = I2C_SIMPLE_WRITE // write address first, then switch to read }; // Address and data are least-significant-byte first i2c_read_data[0] = (uint8_t)((addr & 0x000000FF) >> 0); i2c_read_data[1] = (uint8_t)((addr & 0x0000FF00) >> 8); i2c_read_data[2] = (uint8_t)((addr & 0x00FF0000) >> 16); i2c_read_data[3] = (uint8_t)((addr & 0xFF000000) >> 24); i2c_lock(DDIC_I2C()); retval = i2c_transact_blocking(DDIC_I2C(), &i2ctxn, DEFAULT_I2C_WAIT_TIME); if(retval == pdPASS) { i2ctxn.ttype = I2C_SIMPLE_READ; retval = i2c_transact_blocking(DDIC_I2C(), &i2ctxn, DEFAULT_I2C_WAIT_TIME); } i2c_unlock(DDIC_I2C()); if(retval == pdPASS) { *data = ((uint32_t)(i2c_read_data[0])) + (((uint32_t)(i2c_read_data[1])) << 8) + (((uint32_t)(i2c_read_data[2])) << 16) + (((uint32_t)(i2c_read_data[3])) << 24); } return retval; } // performs the command sequence to the VXR chip static RC_Error_T rc_cmd_set(RC_Command_T cmd, uint32_t length, uint32_t offset, const uint8_t* data, uint32_t timeout) { uint8_t bus_error = pdPASS; uint8_t data_sent; uint8_t bytes_to_copy; uint32_t data_out_buf; // Set data, offset, length, and command first (any order is okay) // fill the data registers // length can be non-zero even without passing in data, for example in memory read commands // have to check if data pointer is null // Packed bytes. The data section is 32 bytes long, organized into 8x 4-byte words. // Data0 is B0 to B3, Data1 is B4-B7, and so on. if(data != NULL) { data_sent = 0; while(data_sent < length) { bytes_to_copy = (length - data_sent) >= 4 ? 4 : (length-data_sent); // 1 to 4 bytes at a time data_out_buf = 0; for(uint8_t i = 0; i < bytes_to_copy; i++) { data_out_buf += ((uint32_t)data[data_sent + i]) << (8*i); } if(bus_error == pdPASS) { bus_error = rc_bus_write(RC_DATA0_ADDR + (data_sent), data_out_buf); } data_sent += bytes_to_copy; } } // write command, length, and offset if(bus_error == pdPASS) { bus_error = rc_bus_write(RC_COMMAND_ADDR, RC_COMMAND_COMMAND(cmd) + RC_COMMAND_ACTIVE); } if(bus_error == pdPASS) { bus_error = rc_bus_write(RC_LENGTH_ADDR, length); } if(bus_error == pdPASS) { bus_error = rc_bus_write(RC_OFFSET_ADDR, offset); } // send the trigger if(bus_error == pdPASS) { bus_error = rc_bus_write(RC_TRIGGER_ADDR, RC_TRIGGER_VALUE); } uint32_t tempval = RC_COMMAND_ACTIVE; // timestamp of when we start uint32_t start_time = rtt_read_timer_value(); // Wait on the command_active bit while((bus_error == pdPASS) && ((tempval & RC_COMMAND_ACTIVE) != 0)) { if(rtt_timeout_check(start_time, timeout)) { return RC_Timeout_Error; } bus_error = rc_bus_read(RC_COMMAND_ADDR, &tempval); } if(bus_error == pdFAIL) { return RC_Bus_Error; } // if everything went well, return the error code from the command register return (uint8_t)((tempval & RC_COMMAND_RESULT_Msk) >> RC_COMMAND_RESULT_Pos); } RC_Error_T VXR_Reset(void) { // Trigger the reset pin on the VXR7200 to force it to reboot ioport_set_pin_level(PIN_DDIC_RST, false); // Active low, "false" asserts reset vTaskDelay(DDIC_RESET_DELAY_MS); ioport_set_pin_level(PIN_DDIC_RST, true); // release reset vTaskDelay(VXR_RC_INTERFACE_DELAY); return VXR_Start_Interface(); } RC_Error_T VXR_Start_Interface(void) { // Send the RC_ENABLE command // requires a specific data sequence of 5 32-bit words: // "V", "X", "R", "0", "0" RC_Error_T retval; uint8_t enable_data[5] = {'V','X','R','0','0'}; retval = rc_cmd_set(Enable_RC, 5, 0, enable_data, RC_DEFAULT_TIMEOUT); if(RC_Success == retval) { vxr_interface_started = true; } return retval; } RC_Error_T VXR_Stop_Interface(void) { // send the RC_DISABLE command RC_Error_T retval; retval= rc_cmd_set(Disable_RC, 0, 0, NULL, RC_DEFAULT_TIMEOUT); if(retval == RC_Success) { vxr_interface_started = false; } return retval; } RC_Error_T VXR_Read_Register(uint32_t regaddr, uint32_t* result) { RC_Error_T retval; uint8_t buserror; retval = rc_cmd_set(Memory_Read, 4, regaddr, NULL, RC_DEFAULT_TIMEOUT); if(retval == RC_Success) { buserror = rc_bus_read(RC_DATA0_ADDR, result); if((buserror == pdPASS)) { return retval; } else { return RC_Bus_Error; } } return retval; } RC_Error_T VXR_Write_Register(uint32_t regaddr, uint32_t data) { uint8_t data_bytes[4]; data_bytes[0] = data & 0x000000FF; data_bytes[1] = (data & 0x0000FF00) >> 8; data_bytes[2] = (data & 0x00FF0000) >> 16; data_bytes[3] = (data & 0xFF000000) >> 24; return rc_cmd_set(Memory_Write, 4, regaddr, data_bytes, RC_DEFAULT_TIMEOUT); } // Confirms there is a valid connection to the VXR7200 by // checking that the RC interface is successfully started RC_Error_T VXR_Check_Connection(void) { if(vxr_interface_started) { return RC_Success; } else { // attempt to start the interface return VXR_Start_Interface(); } } RC_Error_T VXR_Set_EDID(const uint8_t* new_edid, uint16_t edid_size) { uint32_t tempreg; RC_Error_T retval = RC_Success; if((edid_size == 0) || (edid_size > 256)) { // max of 2 EDID blocks return RC_Invalid_Arg; } for(uint16_t i = 0; i < (edid_size/sizeof(uint32_t)); i++) { tempreg = (((uint32_t)new_edid[i*sizeof(uint32_t) + 0]) << 0) + (((uint32_t)new_edid[i*sizeof(uint32_t) + 1]) << 8) + (((uint32_t)new_edid[i*sizeof(uint32_t) + 2]) << 16) + (((uint32_t)new_edid[i*sizeof(uint32_t) + 3]) << 24); if(retval == RC_Success) { retval = VXR_Write_Register(0x3000 + i*sizeof(uint32_t), tempreg); } } return retval; } RC_Error_T VXR_Get_PPS_TXn(uint8_t* rec_pps, uint8_t TX_Sel) { RC_Error_T retval = RC_Success; // Start address // ** TX0_PPS_ADDRESS + PPS_OFFSET // End address // ** TX0_PPS_ADDRESS + PPS_OFFSET + PPS_LENGTH - 1 uint32_t word_to_get; uint32_t current_read_pointer; uint32_t last_byte; if(TX_Sel == 0) { current_read_pointer = TX0_PPS_ADDRESS + PPS_OFFSET; last_byte = TX0_PPS_ADDRESS + PPS_OFFSET + PPS_LENGTH; } else if(TX_Sel == 1) { current_read_pointer = TX1_PPS_ADDRESS + PPS_OFFSET; last_byte = TX1_PPS_ADDRESS + PPS_OFFSET + PPS_LENGTH; } else { return RC_Invalid_Arg; } uint32_t word; uint8_t word_byte[4]; uint8_t rec_pps_pos = 0; while(current_read_pointer < last_byte) { word_to_get = current_read_pointer - (current_read_pointer % sizeof(uint32_t)); // aligns to 4 byte boundary if(retval == RC_Success) { retval = VXR_Read_Register(word_to_get, &word); if(retval == RC_Success) { word_byte[0] = (uint8_t)(word & 0x000000FFu); word_byte[1] = (uint8_t)((word & 0x0000FF00u) >> 8); word_byte[2] = (uint8_t)((word & 0x00FF0000u) >> 16); word_byte[3] = (uint8_t)((word & 0xFF000000u) >> 24); uint8_t start_pos = (current_read_pointer - word_to_get); // position within word_bytes, ie 0 to 3 uint8_t num_of_bytes_to_copy = Min( Min((last_byte - word_to_get), (4u - start_pos)), 4u); memcpy(&(rec_pps[rec_pps_pos]), &(word_byte[start_pos]), num_of_bytes_to_copy); current_read_pointer += num_of_bytes_to_copy; rec_pps_pos += num_of_bytes_to_copy; } } if(retval != RC_Success) { // break out of the loop on failure return retval; } } return retval; } bool VXR_Get_TXn_Video_Status(uint8_t TX_Sel) { uint32_t read_reg; uint32_t reg_value; if(TX_Sel == 0) { read_reg = TX0_VID_MODE_ADDRESS; } else if(TX_Sel == 1) { read_reg = TX1_VID_MODE_ADDRESS; } else { return false; } if(RC_Success == VXR_Read_Register(read_reg, ®_value)) { if((reg_value & TXn_VID_MODE_ENABLED) != 0) { return true; } } return false; } RC_Error_T VXR_Get_Rx_Video_Format(Video_Format_T* fmt) { RC_Error_T retval; uint32_t tempval; float internal_clock = 0.0f; float framerate_working_data = 0.0f; // Query the received video format (height, width, framerate) // Htotal/Vtotal retval = VXR_Read_Register(RX_FRM0_MSA_TOTALS_ADDRESS, &tempval); if(RC_Success == retval) { fmt->Htotal = (uint16_t)((tempval & RX_FRMn_MSA_HTOTAL_Msk) >> RX_FRMn_MSA_HTOTAL_Pos); fmt->Vtotal = (uint16_t)((tempval & RX_FRMn_MSA_VTOTAL_Msk) >> RX_FRMn_MSA_VTOTAL_Pos); retval = VXR_Read_Register(RX_FRM0_MSA_ACTIVE_ADDRESS, &tempval); } if(RC_Success == retval) { fmt->Hactive = (uint16_t)((tempval & RX_FRMn_MSA_HACTIVE_Msk) >> RX_FRMn_MSA_HACTIVE_Pos); fmt->Vactive = (uint16_t)((tempval & RX_FRMn_MSA_VACTIVE_Msk) >> RX_FRMn_MSA_VACTIVE_Pos); retval = VXR_Read_Register(RX_FRM0_BPC_ADDRESS, &tempval); } if(RC_Success == retval) { fmt->ColorBPC = (uint8_t)((tempval & RX_FRMn_BPC_Msk) >> RX_FRMn_BPC_Pos); retval = VXR_Read_Register(RX_INT_CLK_REG, &tempval); } if(RC_Success == retval) { internal_clock = 10000.0f * (float)((tempval & RX_INT_CLK_Msk) >> RX_INT_CLK_Pos); retval = VXR_Read_Register(RX_PIXCLK_MULT_REG, &tempval); } if(RC_Success == retval) { framerate_working_data = internal_clock*100.0f*((float)((tempval & RX_PIXCLK_MULT_Msk) >> RX_PIXCLK_MULT_Pos)); retval = VXR_Read_Register(RX_PIXCLK_DIV_REG, &tempval); } if(RC_Success == retval) { framerate_working_data = framerate_working_data / ((float)((tempval & RX_PIXCLK_DIV_Msk) >> RX_PIXCLK_DIV_Pos)); fmt->Frame_100 = (uint16_t)(framerate_working_data*100.0f); } return retval; } RC_Error_T VXR_Get_DP_Link_Rate_Lane_Count(uint8_t* link_rate, uint8_t* lane_count) { RC_Error_T retval; uint32_t tempval; retval = VXR_Read_Register(RX_LINK_RATE, &tempval); if(RC_Success == retval) { *link_rate = (tempval & 0xFFu); *lane_count = ((tempval >> 8u) & 0x3Fu); } return retval; } RC_Error_T VXR_Get_Rx_Error_Counter(uint8_t DP_Lane_Num, uint16_t* errcount) { // Notes: // Lane number can only be 0 to 3 // errcount will usually have bit 16 (0x8000) set true. this just indicates // that the error count is valid. The lower 15 bits (0x7FFF) are the count // of received errors. RC_Error_T retval; uint32_t regaddr; uint32_t regval; if(DP_Lane_Num > 3) { return RC_Invalid_Arg; } regaddr = RX_ML_PHY_ERR_COUNTERS_0 + (DP_Lane_Num*4); retval = VXR_Read_Register(regaddr, ®val); *errcount = (uint16_t) (regval & 0x0000FFFFu); return retval; } RC_Error_T VXR_Disable_DSC(void) { // Turns off DSC by changing the DisplayPort Configuration Data (DPCD) // register so it tells the GPU that DSC isn't available uint32_t reg_dsc; RC_Error_T errval; errval = VXR_Read_Register(0x1730, ®_dsc); if(RC_Success == errval) { reg_dsc &= ~(0x06); // Turns off bits 1 and 2, which are DSC enable and FEC enable errval = VXR_Write_Register(0x1730, reg_dsc); } return errval; } RC_Error_T VXR_Reset_Rx(void) { // Sends a reset command to the RX status // This causes displays to reset (at least the TX portion of // the VXR7200 will reset) to recover from an interrupted // MIPI stream, for example when OLED power is re-enabled // This does not show upstream as a DisplayPort disconnect, the PC // will think the Headset is still connected uint32_t reg_rx_status; RC_Error_T errval; errval = VXR_Read_Register(RX0_STATUS, ®_rx_status); if(RC_Success == errval) { reg_rx_status &= 0xFFFFFF00u; // reset lowest byte reg_rx_status |= 0x00000001u; // and set the byte to 1 errval = VXR_Write_Register(RX0_STATUS, reg_rx_status); } return errval; } /********* VXR7200 Flash Memory ********* * * There are 8 Flash blocks, numbered 0 through 7 * The total firmware size is 512kB (0x80000) * Flash block mapping: * 0: 0x00000 through 0x0FFFF * 1: 0x10000 through 0x1FFFF * 2: 0x20000 through 0x2FFFF * 3: 0x30000 through 0x3FFFF * 4: 0x40000 through 0x4FFFF * 5: 0x50000 through 0x5FFFF * 6: 0x60000 through 0x6FFFF * 7: 0x70000 through 0x7FFFF * * The Flash is divided into two config/firmware sections and a "ESM" section for HDCP 2.2 * Bank0 is 0x00000 through 0x1FFFF (blocks 0 and 1), Bank1 is 0x20000 through 0x3FFFF * (blocks 2 and 3), and ESM is 0x40000 through 0x7FFFF (blocks 4, 5, 6, and 7). * * Each bank has a Config section for the first 32k and a Firmware section for the next 96k * Config, Firmware, and ESM mapping: * Config, Bank0: 0x00000 through 0x07FFF * FW, Bank0: 0x08000 through 0x1FFFF * Config, Bank1: 0x20000 through 0x27FFF * FW, Bank0: 0x28000 through 0x3FFFF * ESM: 0x40000 through 0x7FFFF * * The last 16 bytes of Config and Firmware are the tag, used by the ROM bootloader to determine * which bank to load. The Synaptics firmware generation tool creates the tag. So a valid tag * can simply be copied from the generated *.fullrom file to the tag location: * Tag byte locations: * Config, Bank0: 0x07FF0 through 0x07FFF * FW, Bank0: 0x1FFF0 through 0x1FFFF * Config, Bank1: 0x27FF0 through 0x27FFF * FW, Bank0: 0x3FFF0 through 0x3FFFF * To invalidate the tag (forcing the bootloader to load the other bank), simply set the first * and last bytes of the tag to zero. */ /** VXR_Calc_Checksum * Calculates the checksum on firmware already in the Flash memory on * the VXR7200 chip. * Checksum calculation is a simple 32-bit sum of all the memory starting * at starting address "start_addr" for length "len". */ RC_Error_T VXR_Calc_Checksum(uint32_t start_addr, uint32_t len, uint32_t* result) { RC_Error_T retval; uint8_t buserror; // checksum calculation appears to take about 1us per count plus a fixed 755us. // So for example, counting a length of 10000 should take about 10755us (10.7ms) // I'm just going to double the timeout to play it safe. Who knows if the VXR7200 // might be doing something funky at the time we ask for a checksum. // Note that the timeout is in ticks of a 10kHz clock. So 10 = 1ms. uint32_t estimated_timeout = len / 50 + 15; retval = rc_cmd_set(Get_FW_Chksum, len, start_addr, NULL, estimated_timeout); if(retval == RC_Success) { buserror = rc_bus_read(RC_DATA0_ADDR, result); if((buserror == pdPASS)) { return retval; } else { return RC_Bus_Error; } } return retval; } /** VXR_Program_Firmware * Programs "len" number of bytes in firmware starting at "start_addr". * Note that to program any '1' bit, the existing bit must already be '1'. * This is standard for Flash memory. Programming can only set '1' to '0', and * to set back to '1' an erase must be performed. Erase can only be done on a * block, not individual bytes. Programming can be done on a single byte. */ RC_Error_T VXR_Program_Firmware(uint32_t start_addr, uint32_t len, const uint8_t* buf) { return rc_cmd_set(Write_Flash, len, start_addr, (uint8_t*)buf, RC_PROGRAM_TIMEOUT); } /** VXR_Erase_Firmware_Bank * Erases the bank (valid 0 through 7, see notes above) and sets * all bits back to '1'. */ RC_Error_T VXR_Erase_Firmware_Bank(uint8_t bank_num) { uint8_t erase_buf[2]; if(bank_num > 7) { return RC_Invalid_Arg; } erase_buf[0] = bank_num; erase_buf[1] = 0x30; // block erase, 64kB return rc_cmd_set(Erase_Flash, 2, 0, erase_buf, RC_ERASE_TIMEOUT); } /** VXR_Erase_Firmware_Bank * Erases the sector (valid 0 through 127, see notes above) and sets * all bits back to '1'. */ RC_Error_T VXR_Erase_Firmware_Sector(uint8_t sector_num) { uint8_t erase_buf[2]; if(sector_num > 127) { return RC_Invalid_Arg; } erase_buf[0] = sector_num; erase_buf[1] = 0x10; // sector erase, 4kB return rc_cmd_set(Erase_Flash, 2, 0, erase_buf, RC_ERASE_TIMEOUT); } /** VXR_Tag_Valid * Determines if the tag region (last 16 bytes of any Config or Firmware region) * is valid. This tag is used by the VXR7200 bootloader to load one of two * config or firmware regions. The config and FW are independent, they can be * updated separately. So that means it's perfectly valid to be operating * with Config0 and FW1, or Config1 and FW0, or both 0, or both 1. * The tag is invalidated by setting the first and last bytes to zero. So * if they are both nonzero, we are considering it a valid tag. * One exception...erased memory is invalid. If it's all 0xFF for the * entire 16 bytes of the tag, it's invalid. * * Returns: true (tag is valid), or false (tag is invalid or * operation failed) */ bool VXR_Tag_Valid(Vxr_Flash_Tag_T region) { // We have a hacky workaround to reading flash bytes. The // "Firmware Data Read Command" listed in the Remote Control // user manual for the VXR7200 doesn't appear to work on the // built-in flash memory. It might be for external SPI flash // (unclear for most of the flash operations if they are // internal or external). But we can read a single byte // at a time by running a checksum command with length of 1. uint32_t flash_addr; // Read the 16 bytes of the tag switch(region) { case Vxr_Tag_Config_0: flash_addr = VXR_CONFIG_0_TAG_START; break; case Vxr_Tag_Config_1: flash_addr = VXR_CONFIG_1_TAG_START; break; case Vxr_Tag_Firmware_0: flash_addr = VXR_FIRMWARE_0_TAG_START; break; case Vxr_Tag_Firmware_1: flash_addr = VXR_FIRMWARE_1_TAG_START; break; default: return false; break; } uint32_t temp_flash_data; bool is_erased = true; bool is_valid = true; for(uint8_t i = 0; i < 16; i++) { if(RC_Success != VXR_Calc_Checksum(flash_addr + i, 1, &temp_flash_data)) { return false; } // Check for erased memory if(is_erased) { if(((uint8_t)temp_flash_data) != 0xFF) is_erased = false; } // Check for first and last tags zero'ed // Only valid if both are nonzero. And not erased. That too. if((i == 0) || (i == 15)) { if(((uint8_t)temp_flash_data) == 0x00) is_valid = false; } } return (is_valid && (!is_erased)); } /** VXR_Get_Firmware_Name * Reads the firmware name saved in the config region of VXR7200 Flash. * First determines the active config (if none, returns an error), then * reads the Flash data by using the hacky checksum workaround. * Since the name is only 15 bytes, this doesn't take very long. * The argument "fwname" should point to an array at least 15 characters long. * This function will not append any zero characters to the end of the string. * If you wanna use C string functions, please do that first. */ RC_Error_T VXR_Get_Firmware_Name(uint8_t* fwname) { RC_Error_T retval = RC_Success; uint32_t flashaddr; uint32_t temp_flash_data; if(VXR_Tag_Valid(Vxr_Tag_Config_0)) { flashaddr = 0x005F0u; }else if(VXR_Tag_Valid(Vxr_Tag_Config_1)) { flashaddr = 0x205F0u; } else { return RC_Unknown_Error; } // Read 15 bytes from Flash by computing the checksum on a single byte, 15 times. for(uint8_t i = 0; i < 15; i++) { if(RC_Success == retval) { retval = VXR_Calc_Checksum(flashaddr + i, 1, &temp_flash_data); if(RC_Success == retval) { fwname[i] = (uint8_t)(temp_flash_data & 0xFFu); } } } return retval; } RC_Error_T VXR_MIPI_Write(uint8_t which_disp, uint32_t len, uint8_t* data) { RC_Error_T retval = RC_Success; if(len > 32) return RC_Unsupported; retval = rc_cmd_set(MIPI_Write, len, ((which_disp==0) ? 0x100 : 0x400), data, RC_DEFAULT_TIMEOUT); return retval; } RC_Error_T VXR_MIPI_Read(uint8_t which_disp, uint32_t in_len, uint8_t* in_data, uint32_t out_len, uint8_t* out_data) { RC_Error_T retval = RC_Success; uint8_t buserror; uint8_t mipi_read_bytes[4]; uint32_t offset; if(which_disp == 0) { offset = 0x100; } else { offset = 0x400; } if((in_len > 32) || (out_len > 32)) return RC_Unsupported; retval = rc_cmd_set(MIPI_Read, in_len, offset, in_data, RC_MIPI_READ_TIMEOUT); if(retval != RC_Success) { return retval; } // Get the read back data packet size buserror = rc_bus_read(RC_DATA0_ADDR, (uint32_t*)mipi_read_bytes); if(pdPASS != buserror) { return RC_Bus_Error; } uint16_t mipi_read_size = ((uint16_t)mipi_read_bytes[0]) + (((uint16_t)mipi_read_bytes[1]) << 8); if(mipi_read_size > out_len) mipi_read_size = out_len; retval = rc_cmd_set(MIPI_Read_From_Buffer, mipi_read_size, offset, NULL, RC_MIPI_READ_TIMEOUT); if(retval != RC_Success) { return retval; } for(uint32_t read_i = 0 ; read_i < mipi_read_size; read_i += 4) { buserror = rc_bus_read(RC_DATA0_ADDR + read_i, (uint32_t*)mipi_read_bytes); if(pdPASS != buserror) { return RC_Bus_Error; } for(uint32_t read_j = 0; (read_j + read_i) < mipi_read_size; read_j++) { out_data[read_j + read_i] = mipi_read_bytes[read_j]; } } return RC_Success; }