| // Copyright 2021 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "ft_firmware.h" | |
| #include "ft_device.h" | |
| namespace { | |
| constexpr uint8_t kFlashStatusReg = 0x6a; | |
| constexpr uint16_t kFlashEccDone = 0xf055; | |
| constexpr uint16_t kFlashEraseDone = 0xf0aa; | |
| constexpr uint8_t kFirmwareEccReg = 0x66; | |
| constexpr uint8_t kBootIdReg = 0x90; | |
| constexpr int kGetBootIdRetries = 10; | |
| constexpr zx::duration kBootIdWaitAfterUnlock = zx::msec(12); | |
| constexpr uint16_t kRombootId = 0x582c; | |
| constexpr uint8_t kChipCoreReg = 0xa3; | |
| constexpr int kGetChipCoreRetries = 6; | |
| constexpr uint8_t kChipCoreFirmwareValid = 0x58; | |
| constexpr uint8_t kFirmwareVersionReg = 0xa6; | |
| constexpr uint8_t kWorkModeReg = 0xfc; | |
| constexpr uint8_t kWorkModeSoftwareReset1 = 0xaa; | |
| constexpr uint8_t kWorkModeSoftwareReset2 = 0x55; | |
| constexpr uint8_t kHidToStdReg = 0xeb; | |
| constexpr uint16_t kHidToStdValue = 0xaa09; | |
| // Commands and parameters | |
| constexpr uint8_t kResetCommand = 0x07; | |
| constexpr zx::duration kResetWait = zx::msec(400); | |
| constexpr uint8_t kFlashEraseCommand = 0x09; | |
| constexpr uint8_t kFlashEraseAppArea = 0x0b; | |
| constexpr uint8_t kUnlockBootCommand = 0x55; | |
| constexpr uint8_t kStartEraseCommand = 0x61; | |
| constexpr zx::duration kEraseWait = zx::msec(1350); | |
| constexpr uint8_t kEccInitializationCommand = 0x64; | |
| constexpr uint8_t kEccCalculateCommand = 0x65; | |
| constexpr uint8_t kFirmwarePacketCommand = 0xbf; | |
| constexpr uint8_t kSetEraseSizeCommand = 0xb0; | |
| // Firmware download | |
| constexpr int kFirmwareDownloadRetries = 2; | |
| constexpr size_t kFirmwareMinSize = 0x120; | |
| constexpr size_t kFirmwareMaxSize = 64l * 1024; | |
| constexpr size_t kFirmwareVersionOffset = 0x10a; | |
| constexpr size_t kMaxPacketAddress = 0x00ff'ffff; | |
| constexpr size_t kMaxPacketSize = 128; | |
| constexpr size_t kMaxEraseSize = 0xfffe; | |
| constexpr zx::duration CalculateEccSleep(const size_t check_size) { | |
| return zx::msec(static_cast<ssize_t>(check_size) / 256); | |
| } | |
| constexpr uint16_t ExpectedWriteStatus(const uint32_t address, const size_t packet_size) { | |
| return (0x1000 + (address / packet_size)) & 0xffff; | |
| } | |
| } // namespace | |
| namespace ft { | |
| uint8_t FtDevice::CalculateEcc(std::span<const uint8_t> buffer, uint8_t initial) { | |
| for (const uint8_t byte : buffer) { | |
| initial ^= byte; | |
| } | |
| return initial; | |
| } | |
| zx_status_t FtDevice::UpdateFirmwareIfNeeded( | |
| const fuchsia_hardware_input_focaltech::Metadata& metadata, display::PanelType panel_type) { | |
| if (!metadata.needs_firmware()) { | |
| return ZX_OK; | |
| } | |
| cpp20::span<const uint8_t> firmware; | |
| const cpp20::span<const FirmwareEntry> entries(kFirmwareEntries, kNumFirmwareEntries); | |
| for (const auto& entry : entries) { | |
| if (entry.panel_type == panel_type) { | |
| firmware = cpp20::span(entry.firmware_data, entry.firmware_size); | |
| break; | |
| } | |
| } | |
| if (firmware.empty()) { | |
| fdf::error("No firmware found for panel type {}", static_cast<uint32_t>(panel_type)); | |
| return ZX_OK; | |
| } | |
| if (firmware.size() < kFirmwareMinSize) { | |
| fdf::error("Firmware binary is too small: {}", firmware.size()); | |
| return ZX_ERR_WRONG_TYPE; | |
| } | |
| if (firmware.size() > kFirmwareMaxSize) { | |
| fdf::error("Firmware binary is too big: {}", firmware.size()); | |
| return ZX_ERR_WRONG_TYPE; | |
| } | |
| const uint8_t firmware_version = firmware[kFirmwareVersionOffset]; | |
| for (int i = 0; i < kFirmwareDownloadRetries; i++) { | |
| const zx::result<bool> firmware_status = CheckFirmwareAndStartRomboot(firmware_version); | |
| if (firmware_status.is_error()) { | |
| fdf::warn("Failed to check firmware and start romboot: {}", firmware_status); | |
| zx::result _ = Write8(kResetCommand); | |
| continue; | |
| } | |
| if (!firmware_status.value()) { | |
| return ZX_OK; | |
| } | |
| if (const zx::result result = EraseFlash(firmware.size()); result.is_error()) { | |
| fdf::warn("Failed to erase flash: {}", result); | |
| zx::result _ = Write8(kResetCommand); | |
| continue; | |
| } | |
| if (const zx::result result = SendFirmware(firmware); result.is_error()) { | |
| fdf::warn("Failed to send firmware: {}", result); | |
| zx::result _ = Write8(kResetCommand); | |
| continue; | |
| } | |
| if (const zx::result result = Write8(kResetCommand); result.is_error()) { | |
| continue; | |
| } | |
| zx::nanosleep(zx::deadline_after(kResetWait)); | |
| fdf::info("Firmware download completed"); | |
| return ZX_OK; | |
| } | |
| fdf::error("Failed to update firmware"); | |
| return ZX_ERR_INTERNAL; | |
| } | |
| zx::result<bool> FtDevice::CheckFirmwareAndStartRomboot(const uint8_t firmware_version) { | |
| bool firmware_valid = false; | |
| for (int i = 0; i < kGetChipCoreRetries; i++) { | |
| const zx::result<uint8_t> chip_core = ReadReg8(kChipCoreReg); | |
| if (chip_core.is_ok() && chip_core.value() == kChipCoreFirmwareValid) { | |
| firmware_valid = true; | |
| break; | |
| } | |
| zx::nanosleep(zx::deadline_after(zx::msec(200))); | |
| } | |
| if (!firmware_valid) { | |
| // Firmware is invalid, the chip must already be in romboot. | |
| return zx::ok(true); | |
| } | |
| const zx::result<uint8_t> current_firmware_version = ReadReg8(kFirmwareVersionReg); | |
| if (current_firmware_version.is_ok() && current_firmware_version.value() == firmware_version) { | |
| // Firmware is valid and the version matches what the driver has, no need to update. | |
| fdf::info("Firmware version is current, skipping download"); | |
| return zx::ok(false); | |
| } | |
| if (current_firmware_version.is_ok()) { | |
| fdf::info("Chip firmware ({:#02x}) doesn't match our version ({:#02x}), starting download", | |
| current_firmware_version.value(), firmware_version); | |
| } else { | |
| fdf::warn("Failed to read chip firmware version, starting download"); | |
| } | |
| zx_status_t status; | |
| if (zx::result result = StartRomboot(); result.is_error()) { | |
| fdf::error("Failed to start romboot: {}", result); | |
| return result.take_error(); | |
| } | |
| if ((status = WaitForRomboot()) != ZX_OK) { | |
| return zx::error_result(status); | |
| } | |
| return zx::ok(true); | |
| } | |
| zx::result<> FtDevice::StartRomboot() { | |
| if (zx::result result = WriteReg8(kWorkModeReg, kWorkModeSoftwareReset1); result.is_error()) { | |
| return result.take_error(); | |
| } | |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); | |
| if (zx::result result = WriteReg8(kWorkModeReg, kWorkModeSoftwareReset2); result.is_error()) { | |
| return result.take_error(); | |
| } | |
| zx::nanosleep(zx::deadline_after(zx::msec(80))); | |
| return zx::ok(); | |
| } | |
| zx_status_t FtDevice::WaitForRomboot() { | |
| zx::result<uint16_t> boot_id; | |
| for (int i = 0; i < kGetBootIdRetries; i++) { | |
| boot_id = GetBootId(); | |
| if (boot_id.is_ok() && boot_id.value() == kRombootId) { | |
| return ZX_OK; | |
| } | |
| } | |
| if (boot_id.is_error()) { | |
| return boot_id.error_value(); | |
| } | |
| if (boot_id.value() != kRombootId) { | |
| fdf::error("Timed out waiting for boot ID {:#04x}, got {:#04x}", kRombootId, boot_id.value()); | |
| return ZX_ERR_TIMED_OUT; | |
| } | |
| return ZX_OK; | |
| } | |
| zx::result<uint16_t> FtDevice::GetBootId() { | |
| zx::result _ = WriteReg16(kHidToStdReg, kHidToStdValue); | |
| if (zx::result result = Write8(kUnlockBootCommand); result.is_error()) { | |
| fdf::error("Failed to send unlock command: {}", result); | |
| return result.take_error(); | |
| } | |
| zx::nanosleep(zx::deadline_after(kBootIdWaitAfterUnlock)); | |
| return ReadReg16(kBootIdReg); | |
| } | |
| zx::result<bool> FtDevice::WaitForFlashStatus(const uint16_t expected_value, const int tries, | |
| const zx::duration retry_sleep) { | |
| zx::result<uint16_t> value; | |
| for (int i = 0; i < tries; i++) { | |
| value = ReadReg16(kFlashStatusReg); | |
| if (value.is_ok() && value.value() == expected_value) { | |
| return zx::ok(true); | |
| } | |
| zx::nanosleep(zx::deadline_after(retry_sleep)); | |
| } | |
| if (value.is_error()) { | |
| return zx::error(value.error_value()); | |
| } | |
| return zx::ok(false); | |
| } | |
| zx::result<> FtDevice::SendFirmwarePacket(const uint32_t address, std::span<const uint8_t> packet) { | |
| constexpr size_t kPacketHeaderSize = 1 + 3 + 2; // command + address + length | |
| if (address > kMaxPacketAddress) { | |
| fdf::error("Packet address {:#08x} is too large: Max packet address is {:#08x}", address, | |
| kMaxPacketAddress); | |
| return zx::error(ZX_ERR_INVALID_ARGS); | |
| } | |
| if (packet.size() > kMaxPacketSize) { | |
| fdf::error("Packet size of {} bytes is too large: Max packet size is {} bytes", packet.size(), | |
| kMaxPacketSize); | |
| return zx::error(ZX_ERR_INVALID_ARGS); | |
| } | |
| std::array<uint8_t, kPacketHeaderSize + kMaxPacketSize> packet_buffer = { | |
| kFirmwarePacketCommand, | |
| static_cast<uint8_t>((address >> 16) & 0xff), | |
| static_cast<uint8_t>((address >> 8) & 0xff), | |
| static_cast<uint8_t>(address & 0xff), | |
| static_cast<uint8_t>((packet.size() >> 8) & 0xff), | |
| static_cast<uint8_t>(packet.size() & 0xff), | |
| }; | |
| memcpy(packet_buffer.data() + kPacketHeaderSize, packet.data(), packet.size()); | |
| zx::result result = | |
| i2c_.WriteSync(std::span(packet_buffer.begin(), kPacketHeaderSize + packet.size())); | |
| if (result.is_error()) { | |
| fdf::error("Failed to write {} bytes to {:#06x}: {}", packet.size(), address, result); | |
| return result.take_error(); | |
| } | |
| return zx::ok(); | |
| } | |
| zx::result<> FtDevice::EraseFlash(const size_t size) { | |
| if (zx::result result = WriteReg8(kFlashEraseCommand, kFlashEraseAppArea); result.is_error()) { | |
| return result.take_error(); | |
| } | |
| std::array<uint8_t, 4> erase_size_buffer; | |
| erase_size_buffer[0] = kSetEraseSizeCommand; | |
| erase_size_buffer[1] = (size >> 16) & 0xff; | |
| erase_size_buffer[2] = (size >> 8) & 0xff; | |
| erase_size_buffer[3] = size & 0xff; | |
| if (zx::result result = i2c_.WriteSync(erase_size_buffer); result.is_error()) { | |
| fdf::error("Failed to write erase size: {}", result); | |
| return result.take_error(); | |
| } | |
| if (zx::result result = Write8(kStartEraseCommand); result.is_error()) { | |
| return result.take_error(); | |
| } | |
| zx::nanosleep(zx::deadline_after(kEraseWait)); | |
| zx::result<bool> erase_done = WaitForFlashStatus(kFlashEraseDone, 50, zx::msec(400)); | |
| if (erase_done.is_error()) { | |
| return erase_done.take_error(); | |
| } | |
| if (!erase_done.value()) { | |
| fdf::error("Timed out waiting for flash erase"); | |
| return zx::error(ZX_ERR_TIMED_OUT); | |
| } | |
| return zx::ok(); | |
| } | |
| zx::result<> FtDevice::SendFirmware(cpp20::span<const uint8_t> firmware) { | |
| size_t offset = 0; | |
| uint8_t expected_ecc = 0; | |
| while (offset < firmware.size()) { | |
| const size_t remaining = firmware.size() - offset; | |
| const size_t send_size = std::min(kMaxPacketSize, remaining); | |
| const uint32_t address = static_cast<uint32_t>(offset); | |
| zx::result result = SendFirmwarePacket(address, firmware.subspan(offset, send_size)); | |
| if (result.is_error()) { | |
| fdf::error("Failed to send firmware packet: {}", result); | |
| return result.take_error(); | |
| } | |
| zx::nanosleep(zx::deadline_after(zx::msec(1))); | |
| const uint16_t expected_status = ExpectedWriteStatus(address, send_size); | |
| zx::result<bool> write_done = WaitForFlashStatus(expected_status, 100, zx::msec(1)); | |
| if (write_done.is_error()) { | |
| return write_done.take_error(); | |
| } | |
| if (!write_done.value()) { | |
| fdf::warn("Timed out waiting for correct flash write status"); | |
| } | |
| expected_ecc = CalculateEcc(firmware.subspan(offset, send_size), expected_ecc); | |
| offset += send_size; | |
| } | |
| zx::result result = CheckFirmwareEcc(firmware.size(), expected_ecc); | |
| if (result.is_error()) { | |
| fdf::error("Failed to check firmware ecc: {}", result); | |
| return result.take_error(); | |
| } | |
| return zx::ok(); | |
| } | |
| zx::result<> FtDevice::CheckFirmwareEcc(const size_t size, const uint8_t expected_ecc) { | |
| if (zx::result result = Write8(kEccInitializationCommand); result.is_error()) { | |
| return result.take_error(); | |
| } | |
| size_t address = 0; | |
| for (size_t bytes_remaining = size; bytes_remaining > 0;) { | |
| const size_t check_size = std::min<size_t>(kMaxEraseSize, bytes_remaining); | |
| const std::array<uint8_t, 6> check_buffer = { | |
| kEccCalculateCommand, | |
| static_cast<uint8_t>((address >> 16) & 0xff), | |
| static_cast<uint8_t>((address >> 8) & 0xff), | |
| static_cast<uint8_t>(address & 0xff), | |
| static_cast<uint8_t>((check_size >> 8) & 0xff), | |
| static_cast<uint8_t>(check_size & 0xff), | |
| }; | |
| zx::result result = i2c_.WriteSync(check_buffer); | |
| if (result.is_error()) { | |
| fdf::error("Failed to send ECC calculate command: {}", result); | |
| return result.take_error(); | |
| } | |
| zx::nanosleep(zx::deadline_after(CalculateEccSleep(check_size))); | |
| zx::result<bool> ecc_done = WaitForFlashStatus(kFlashEccDone, 10, zx::msec(50)); | |
| if (ecc_done.is_error()) { | |
| return ecc_done.take_error(); | |
| } | |
| if (!ecc_done.value()) { | |
| fdf::error("Timed out waiting for ECC calculation"); | |
| return zx::error(ZX_ERR_TIMED_OUT); | |
| } | |
| bytes_remaining -= check_size; | |
| address += check_size; | |
| } | |
| zx::result<uint8_t> ecc = ReadReg8(kFirmwareEccReg); | |
| if (ecc.is_error()) { | |
| return ecc.take_error(); | |
| } | |
| if (ecc.value() != expected_ecc) { | |
| fdf::error("Firmware ECC mismatch, got {:#02x}, expected {:#02x}", ecc.value(), expected_ecc); | |
| return zx::error(ZX_ERR_IO_DATA_LOSS); | |
| } | |
| return zx::ok(); | |
| } | |
| zx::result<uint8_t> FtDevice::ReadReg8(const uint8_t address) { | |
| std::array<uint8_t, 1> value; | |
| zx::result result = i2c_.ReadSync(address, value); | |
| if (result.is_error()) { | |
| fdf::error("Failed to read from {:#02x}: {}", address, result); | |
| return result.take_error(); | |
| } | |
| return zx::ok(value[0]); | |
| } | |
| zx::result<uint16_t> FtDevice::ReadReg16(const uint8_t address) { | |
| std::array<uint8_t, 2> buffer; | |
| zx::result result = i2c_.ReadSync(address, buffer); | |
| if (result.is_error()) { | |
| fdf::error("Failed to read from {:#02x}: {}", address, result); | |
| return result.take_error(); | |
| } | |
| return zx::ok(static_cast<uint16_t>((buffer[0] << 8) | buffer[1])); | |
| } | |
| zx::result<> FtDevice::Write8(const uint8_t value) { | |
| const std::array<uint8_t, 1> write_data = {value}; | |
| zx::result result = i2c_.WriteSync(write_data); | |
| if (result.is_error()) { | |
| fdf::error("Failed to write {:#02x}: {}", value, result); | |
| return result.take_error(); | |
| } | |
| return zx::ok(); | |
| } | |
| zx::result<> FtDevice::WriteReg8(const uint8_t address, const uint8_t value) { | |
| const std::array<uint8_t, 2> write_data = {address, value}; | |
| zx::result result = i2c_.WriteSync(write_data); | |
| if (result.is_error()) { | |
| fdf::error("Failed to write {:#02x} to {:#02x}: {}", value, address, result); | |
| return result.take_error(); | |
| } | |
| return zx::ok(); | |
| } | |
| zx::result<> FtDevice::WriteReg16(const uint8_t address, const uint16_t value) { | |
| const std::array<uint8_t, 3> write_data = { | |
| address, | |
| static_cast<uint8_t>((value >> 8) & 0xff), | |
| static_cast<uint8_t>(value & 0xff), | |
| }; | |
| zx::result result = i2c_.WriteSync(write_data); | |
| if (result.is_error()) { | |
| fdf::error("Failed to write {:#04x} to {:#02x}: {}", value, address, result); | |
| return result.take_error(); | |
| } | |
| return zx::ok(); | |
| } | |
| } // namespace ft |