| // Copyright 2020 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 "gt6853.h" |
| |
| #include <endian.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/profile.h> |
| #include <threads.h> |
| #include <zircon/threads.h> |
| |
| #include <ddktl/fidl.h> |
| #include <fbl/auto_lock.h> |
| |
| #include "src/ui/input/drivers/gt6853/gt6853-bind.h" |
| |
| namespace { |
| |
| constexpr int64_t kMaxContactX = 600; |
| constexpr int64_t kMaxContactY = 1024; |
| |
| constexpr size_t kContactSize = 8; |
| |
| constexpr uint8_t kTouchEvent = 1 << 7; |
| |
| constexpr uint8_t kCpuCtrlHoldSs51 = 0x24; |
| |
| constexpr zx::duration kResetSetupTime = zx::msec(2); |
| |
| constexpr int kFirmwareTries = 200; |
| |
| constexpr size_t kMaxSubsysCount = 28; |
| constexpr size_t kI2cMaxTransferSize = 256; |
| |
| constexpr uint8_t kCpuRunFromFlash[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
| constexpr uint8_t kCpuRunFromRam[8] = {0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}; |
| |
| } // namespace |
| |
| namespace touch { |
| |
| enum class Gt6853Device::HostCommand : uint8_t { |
| kConfigStart = 0x80, |
| kConfigEnd = 0x83, |
| }; |
| |
| enum class Gt6853Device::DeviceCommand : uint8_t { |
| kReadyForConfig = 0x82, |
| kDeviceIdle = 0xff, |
| }; |
| |
| void Gt6853InputReport::ToFidlInputReport( |
| fidl::WireTableBuilder<fuchsia_input_report::wire::InputReport>& input_report, |
| fidl::AnyArena& allocator) { |
| fidl::VectorView<fuchsia_input_report::wire::ContactInputReport> input_contacts(allocator, |
| num_contacts); |
| for (size_t i = 0; i < num_contacts; i++) { |
| auto contact = fuchsia_input_report::wire::ContactInputReport::Builder(allocator); |
| contact.contact_id(contacts[i].contact_id); |
| contact.position_x(contacts[i].position_x); |
| contact.position_y(contacts[i].position_y); |
| input_contacts[i] = contact.Build(); |
| } |
| |
| auto touch_report = fuchsia_input_report::wire::TouchInputReport::Builder(allocator); |
| touch_report.contacts(input_contacts); |
| |
| input_report.event_time(event_time.get()); |
| input_report.touch(touch_report.Build()); |
| } |
| |
| zx_status_t Gt6853Device::Create(void* ctx, zx_device_t* parent) { |
| ddk::I2cChannel i2c(parent, "i2c"); |
| if (!i2c.is_valid()) { |
| zxlogf(ERROR, "Failed to get I2C fragment"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| ddk::GpioProtocolClient interrupt_gpio(parent, "gpio-int"); |
| if (!interrupt_gpio.is_valid()) { |
| zxlogf(ERROR, "Failed to get interrupt GPIO fragment"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| ddk::GpioProtocolClient reset_gpio(parent, "gpio-reset"); |
| if (!reset_gpio.is_valid()) { |
| zxlogf(ERROR, "Failed to get reset GPIO fragment"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| std::unique_ptr<Gt6853Device> device = |
| std::make_unique<Gt6853Device>(parent, std::move(i2c), interrupt_gpio, reset_gpio); |
| if (!device) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t status = device->Init(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = device->DdkAdd(ddk::DeviceAddArgs("gt6853").set_inspect_vmo( |
| device->inspector_.DuplicateVmo()))) != ZX_OK) { |
| zxlogf(ERROR, "DdkAdd failed: %d", status); |
| return status; |
| } |
| |
| __UNUSED auto _ = device.release(); |
| return ZX_OK; |
| } |
| |
| void Gt6853Device::DdkUnbind(ddk::UnbindTxn txn) { |
| Shutdown(); |
| txn.Reply(); |
| } |
| |
| void Gt6853Device::GetInputReportsReader(GetInputReportsReaderRequestView request, |
| GetInputReportsReaderCompleter::Sync& completer) { |
| zx_status_t status = |
| input_report_readers_.CreateReader(loop_.dispatcher(), std::move(request->reader)); |
| if (status == ZX_OK) { |
| sync_completion_signal(&next_reader_wait_); // Only for tests. |
| } |
| } |
| |
| void Gt6853Device::GetDescriptor(GetDescriptorRequestView request, |
| GetDescriptorCompleter::Sync& completer) { |
| constexpr size_t kDescriptorBufferSize = 512; |
| |
| constexpr fuchsia_input_report::wire::Axis kAxisX = { |
| .range = {.min = 0, .max = kMaxContactX}, |
| .unit = {.type = fuchsia_input_report::wire::UnitType::kNone, .exponent = 0}, |
| }; |
| |
| constexpr fuchsia_input_report::wire::Axis kAxisY = { |
| .range = {.min = 0, .max = kMaxContactY}, |
| .unit = {.type = fuchsia_input_report::wire::UnitType::kNone, .exponent = 0}, |
| }; |
| |
| fidl::Arena<kDescriptorBufferSize> allocator; |
| |
| fuchsia_input_report::wire::DeviceInfo device_info; |
| device_info.vendor_id = static_cast<uint32_t>(fuchsia_input_report::wire::VendorId::kGoogle); |
| device_info.product_id = static_cast<uint32_t>( |
| fuchsia_input_report::wire::VendorGoogleProductId::kFocaltechTouchscreen); |
| |
| fidl::VectorView<fuchsia_input_report::wire::ContactInputDescriptor> touch_input_contacts( |
| allocator, kMaxContacts); |
| for (uint32_t i = 0; i < kMaxContacts; i++) { |
| touch_input_contacts[i] = fuchsia_input_report::wire::ContactInputDescriptor::Builder(allocator) |
| .position_x(kAxisX) |
| .position_y(kAxisY) |
| .Build(); |
| } |
| |
| auto touch_input_descriptor = |
| fuchsia_input_report::wire::TouchInputDescriptor::Builder(allocator); |
| touch_input_descriptor.contacts(touch_input_contacts); |
| touch_input_descriptor.max_contacts(kMaxContacts); |
| touch_input_descriptor.touch_type(fuchsia_input_report::wire::TouchType::kTouchscreen); |
| |
| auto touch_descriptor = fuchsia_input_report::wire::TouchDescriptor::Builder(allocator); |
| touch_descriptor.input(touch_input_descriptor.Build()); |
| |
| auto descriptor = fuchsia_input_report::wire::DeviceDescriptor::Builder(allocator); |
| descriptor.device_info(device_info); |
| descriptor.touch(touch_descriptor.Build()); |
| |
| completer.Reply(descriptor.Build()); |
| } |
| |
| void Gt6853Device::SendOutputReport(SendOutputReportRequestView request, |
| SendOutputReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void Gt6853Device::GetFeatureReport(GetFeatureReportRequestView request, |
| GetFeatureReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void Gt6853Device::SetFeatureReport(SetFeatureReportRequestView request, |
| SetFeatureReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| void Gt6853Device::GetInputReport(GetInputReportRequestView request, |
| GetInputReportCompleter::Sync& completer) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void Gt6853Device::WaitForNextReader() { |
| sync_completion_wait(&next_reader_wait_, ZX_TIME_INFINITE); |
| sync_completion_reset(&next_reader_wait_); |
| } |
| |
| Gt6853Contact Gt6853Device::ParseContact(const uint8_t* const contact_buffer) { |
| Gt6853Contact ret = {}; |
| ret.contact_id = contact_buffer[0] & 0b1111; |
| ret.position_x = contact_buffer[1] | (contact_buffer[2] << 8); |
| ret.position_y = contact_buffer[3] | (contact_buffer[4] << 8); |
| return ret; |
| } |
| |
| zx_status_t Gt6853Device::Init() { |
| root_ = inspector_.GetRoot().CreateChild("gt6853"); |
| firmware_status_ = root_.CreateString("firmware_status", "initialization failed"); |
| config_status_ = root_.CreateString("config_status", "initialization failed"); |
| |
| // These names must match the strings in //src/diagnostics/config/sampler/input.json. |
| metrics_root_ = inspector_.GetRoot().CreateChild("hid-input-report-touch"); |
| average_latency_usecs_ = metrics_root_.CreateUint("average_latency_usecs", 0); |
| max_latency_usecs_ = metrics_root_.CreateUint("max_latency_usecs", 0); |
| |
| zx_status_t status = interrupt_gpio_.ConfigIn(GPIO_NO_PULL); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ConfigIn failed: %d", status); |
| return status; |
| } |
| |
| if ((status = interrupt_gpio_.GetInterrupt(ZX_INTERRUPT_MODE_EDGE_LOW, &interrupt_)) != ZX_OK) { |
| zxlogf(ERROR, "GetInterrupt failed: %d", status); |
| return status; |
| } |
| |
| zx::status<fuchsia_mem::wire::Range> config = GetConfigFileVmo(); |
| if (config.is_error()) { |
| return config.status_value(); |
| } |
| |
| if (config->vmo.is_valid()) { |
| if ((status = UpdateFirmwareIfNeeded()) != ZX_OK) { |
| firmware_status_.Set("failed"); |
| return status; |
| } |
| |
| if ((status = DownloadConfigIfNeeded(*config)) != ZX_OK) { |
| config_status_.Set("failed"); |
| return status; |
| } |
| } else { |
| zxlogf(INFO, "No device metadata, assuming mexec and preserving controller state"); |
| firmware_status_.Set("skipped"); |
| } |
| |
| status = thrd_create_with_name( |
| &thread_, [](void* arg) -> int { return reinterpret_cast<Gt6853Device*>(arg)->Thread(); }, |
| this, "gt6853-thread"); |
| if (status != thrd_success) { |
| zxlogf(ERROR, "Failed to create thread: %d", status); |
| return thrd_status_to_zx_status(status); |
| } |
| |
| // Copied from //src/ui/input/drivers/focaltech/ft_device.cc |
| |
| // Set profile for device thread. |
| // TODO(fxbug.dev/40858): Migrate to the role-based API when available, instead of hard |
| // coding parameters. |
| { |
| const zx::duration capacity = zx::usec(200); |
| const zx::duration deadline = zx::msec(1); |
| const zx::duration period = deadline; |
| |
| zx::profile profile; |
| status = device_get_deadline_profile(zxdev(), capacity.get(), deadline.get(), period.get(), |
| "gt6853-thread", profile.reset_and_get_address()); |
| if (status != ZX_OK) { |
| zxlogf(WARNING, "Failed to get deadline profile: %d", status); |
| } else { |
| status = zx_object_set_profile(thrd_get_zx_handle(thread_), profile.get(), 0); |
| if (status != ZX_OK) { |
| zxlogf(WARNING, "Failed to apply deadline profile to device thread: %d", status); |
| } |
| } |
| } |
| |
| if ((status = loop_.StartThread("gt6853-reader-thread")) != ZX_OK) { |
| zxlogf(ERROR, "Failed to start loop: %d", status); |
| Shutdown(); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Gt6853Device::DownloadConfigIfNeeded(const fuchsia_mem::wire::Range& config_file) { |
| zx::status<uint8_t> sensor_id = ReadReg8(Register::kSensorIdReg); |
| if (sensor_id.is_error()) { |
| zxlogf(ERROR, "Failed to read sensor ID register: %d", sensor_id.error_value()); |
| return sensor_id.error_value(); |
| } |
| |
| zxlogf(INFO, "Sensor ID 0x%02x", sensor_id.value()); |
| sensor_id_ = root_.CreateInt("sensor_id", sensor_id.value()); |
| |
| fzl::VmoMapper mapped_config; |
| zx_status_t status = mapped_config.Map(config_file.vmo, 0, config_file.size, ZX_VM_PERM_READ); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to map config VMO: %d", status); |
| return status; |
| } |
| |
| zx::status<uint64_t> config_offset = GetConfigOffset(mapped_config, sensor_id.value() & 0xf); |
| if (config_offset.is_error()) { |
| return config_offset.error_value(); |
| } |
| |
| uint32_t config_size = 0; |
| if (config_file.size < config_offset.value() + sizeof(config_size)) { |
| zxlogf(ERROR, "Config VMO size is %zu, must be at least %lu", config_file.size, |
| config_offset.value() + sizeof(config_size)); |
| return ZX_ERR_IO_INVALID; |
| } |
| |
| const uint8_t* const config_data = reinterpret_cast<uint8_t*>(mapped_config.start()); |
| memcpy(&config_size, config_data + config_offset.value(), sizeof(config_size)); |
| config_size = le32toh(config_size); |
| |
| // The offset of the config data in each config table entry. |
| constexpr uint32_t kConfigDataOffset = 121; |
| |
| if (config_size < kConfigDataOffset) { |
| zxlogf(ERROR, "Config size is %u, must be at least %u", config_size, kConfigDataOffset); |
| return ZX_ERR_IO_INVALID; |
| } |
| |
| zxlogf(INFO, "Found %u-byte config at offset %lu", config_size, config_offset.value()); |
| |
| cpp20::span<const uint8_t> config{config_data + config_offset.value() + kConfigDataOffset, |
| config_size - kConfigDataOffset}; |
| return SendConfig(config); |
| } |
| |
| zx::status<uint64_t> Gt6853Device::GetConfigOffset(const fzl::VmoMapper& mapped_config, |
| const uint8_t sensor_id) { |
| constexpr size_t kConfigTableHeaderSize = 16; |
| if (mapped_config.size() < kConfigTableHeaderSize) { |
| zxlogf(ERROR, "Config VMO size is %zu, must be at least %zu", mapped_config.size(), |
| kConfigTableHeaderSize); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| |
| uint32_t config_size = 0; |
| memcpy(&config_size, mapped_config.start(), sizeof(config_size)); |
| config_size = le32toh(config_size); |
| if (config_size != mapped_config.size()) { |
| zxlogf(ERROR, "Config size (%u) doesn't match VMO size (%zu)", config_size, |
| mapped_config.size()); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| |
| const uint8_t* const config_data = reinterpret_cast<uint8_t*>(mapped_config.start()); |
| |
| uint8_t expected_checksum = 0; |
| for (uint64_t i = sizeof(config_size) + 1; i < mapped_config.size(); i++) { |
| expected_checksum += config_data[i]; |
| } |
| |
| if (config_data[sizeof(config_size)] != expected_checksum) { |
| zxlogf(ERROR, "Config checksum doesn't match calculated value"); |
| return zx::error(ZX_ERR_IO_DATA_INTEGRITY); |
| } |
| |
| // The offset of the config entry count in the table header. |
| constexpr uint64_t kConfigEntryCountOffset = 9; |
| |
| const uint8_t config_count = config_data[kConfigEntryCountOffset]; |
| |
| if (mapped_config.size() < (kConfigTableHeaderSize + (config_count * sizeof(uint16_t)))) { |
| zxlogf(ERROR, "Config VMO size is %zu, must be at least %zu", mapped_config.size(), |
| kConfigTableHeaderSize + (config_count * sizeof(uint16_t))); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| |
| for (int i = 0; i < config_count; i++) { |
| const uint64_t config_offset_offset = kConfigTableHeaderSize + (i * sizeof(uint16_t)); |
| uint16_t config_offset = 0; |
| memcpy(&config_offset, config_data + config_offset_offset, sizeof(config_offset)); |
| config_offset = le16toh(config_offset); |
| |
| // The offset of the sensor ID in each config table entry. |
| constexpr uint64_t kConfigSensorIdOffset = 20; |
| |
| if (mapped_config.size() < config_offset + kConfigSensorIdOffset) { |
| zxlogf(ERROR, "Config offset %u is too big", config_offset); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| |
| const uint8_t config_sensor_id = config_data[config_offset + kConfigSensorIdOffset]; |
| if (config_sensor_id == sensor_id) { |
| return zx::ok(config_offset); |
| } |
| } |
| |
| zxlogf(ERROR, "Failed to find config for sensor ID 0x%02x", sensor_id); |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| zx_status_t Gt6853Device::PollCommandRegister(const DeviceCommand command) { |
| constexpr int kCommandTimeoutMs = 100; // An arbitrary timeout that seems to work. |
| for (int i = 0; i < kCommandTimeoutMs; i++) { |
| auto status = ReadReg8(Register::kCommandReg); |
| if (status.is_error()) { |
| zxlogf(ERROR, "Failed to read command register"); |
| return status.error_value(); |
| } |
| |
| if (status.value() == static_cast<uint8_t>(command)) { |
| return ZX_OK; |
| } |
| |
| zx::nanosleep(zx::deadline_after(zx::msec(1))); |
| } |
| |
| zxlogf(ERROR, "Timed out waiting for command register 0x%02x", static_cast<uint8_t>(command)); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx_status_t Gt6853Device::SendCommand(const HostCommand command) { |
| const uint8_t checksum = 0xff - static_cast<uint8_t>(command) + 1; |
| uint8_t buffer[] = { |
| static_cast<uint16_t>(Register::kCommandReg) >> 8, |
| static_cast<uint16_t>(Register::kCommandReg) & 0xff, |
| static_cast<uint8_t>(command), |
| 0x00, |
| checksum, |
| }; |
| zx_status_t status = i2c_.WriteSync(buffer, sizeof(buffer)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to send command 0x%02x: %d", static_cast<uint8_t>(command), status); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Gt6853Device::SendConfig(cpp20::span<const uint8_t> config) { |
| zx_status_t status = PollCommandRegister(DeviceCommand::kDeviceIdle); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Device not idle before config download"); |
| return status; |
| } |
| |
| if ((status = SendCommand(HostCommand::kConfigStart)) != ZX_OK) { |
| zxlogf(ERROR, "Failed to start config download"); |
| return status; |
| } |
| |
| if ((status = PollCommandRegister(DeviceCommand::kReadyForConfig)) != ZX_OK) { |
| return status; |
| } |
| |
| constexpr size_t kMaxConfigPacketSize = 128; |
| |
| size_t size = config.size(); |
| size_t offset = 0; |
| while (size > 0) { |
| const size_t tx_size = std::min(size, kMaxConfigPacketSize); |
| uint8_t buffer[sizeof(Register::kConfigDataReg) + kMaxConfigPacketSize]; |
| |
| buffer[0] = static_cast<uint16_t>(Register::kConfigDataReg) >> 8; |
| buffer[1] = static_cast<uint16_t>(Register::kConfigDataReg) & 0xff; |
| memcpy(buffer + 2, config.data() + offset, tx_size); |
| if ((status = i2c_.WriteSync(buffer, tx_size + sizeof(Register::kConfigDataReg))) != ZX_OK) { |
| zxlogf(ERROR, "Failed to write %zu config bytes: %d", tx_size, status); |
| return status; |
| } |
| |
| size -= tx_size; |
| offset += tx_size; |
| } |
| |
| if ((status = SendCommand(HostCommand::kConfigEnd)) != ZX_OK) { |
| zxlogf(ERROR, "Failed to stop config download"); |
| return status; |
| } |
| |
| if ((status = PollCommandRegister(DeviceCommand::kDeviceIdle)) != ZX_OK) { |
| zxlogf(ERROR, "Device not idle after config download"); |
| return status; |
| } |
| |
| config_status_.Set("download succeeded"); |
| return ZX_OK; |
| } |
| |
| zx_status_t Gt6853Device::UpdateFirmwareIfNeeded() { |
| zx::vmo fw_vmo; |
| size_t fw_vmo_size = 0; |
| zx_status_t status = |
| load_firmware(parent(), GT6853_FIRMWARE_PATH, fw_vmo.reset_and_get_address(), &fw_vmo_size); |
| if (status != ZX_OK) { |
| zxlogf(WARNING, "Failed to load firmware binary, skipping firmware update"); |
| firmware_status_.Set("skipped, no firmware found"); |
| return ZX_OK; |
| } |
| |
| fzl::VmoMapper mapped_fw; |
| if ((status = mapped_fw.Map(fw_vmo, 0, fw_vmo_size, ZX_VM_PERM_READ)) != ZX_OK) { |
| zxlogf(ERROR, "Failed to map firmware VMO: %d", status); |
| return status; |
| } |
| |
| FirmwareSubsysInfo subsys_entries[kMaxSubsysCount]; |
| zx::status<size_t> entry_count = ParseFirmwareInfo(mapped_fw, subsys_entries); |
| if (entry_count.is_error()) { |
| return entry_count.error_value(); |
| } |
| |
| cpp20::span<const FirmwareSubsysInfo> subsys_span(subsys_entries, entry_count.value()); |
| if ((status = PrepareFirmwareUpdate(subsys_span)) != ZX_OK) { |
| return status; |
| } |
| |
| for (const FirmwareSubsysInfo& subsys_info : subsys_span.subspan(1)) { |
| if ((status = FlashSubsystem(subsys_info)) != ZX_OK) { |
| return status; |
| } |
| } |
| |
| return FinishFirmwareUpdate(); |
| } |
| |
| zx::status<size_t> Gt6853Device::ParseFirmwareInfo(const fzl::VmoMapper& mapped_fw, |
| FirmwareSubsysInfo* out_subsys_entries) { |
| constexpr size_t kFirmwareHeaderSize = 32; |
| constexpr size_t kSubsysCountOffset = 27; |
| constexpr size_t kSubsysEntrySize = 8; |
| constexpr size_t kSubsysDataOffset = kFirmwareHeaderSize + (kMaxSubsysCount * kSubsysEntrySize); |
| |
| if (mapped_fw.size() < kSubsysDataOffset) { |
| zxlogf(ERROR, "Firmware VMO size is %zu, must be at least %zu", mapped_fw.size(), |
| kSubsysDataOffset); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| |
| const uint8_t* fw_data = static_cast<const uint8_t*>(mapped_fw.start()); |
| |
| uint32_t fw_size; |
| memcpy(&fw_size, fw_data, sizeof(fw_size)); |
| fw_size = be32toh(fw_size); |
| |
| uint16_t checksum; |
| |
| if (fw_size + sizeof(fw_size) + sizeof(checksum) != mapped_fw.size()) { |
| zxlogf(ERROR, "Firmware header indicates size %zu, but VMO size is %zu", |
| fw_size + sizeof(fw_size) + sizeof(checksum), mapped_fw.size()); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| |
| memcpy(&checksum, fw_data + sizeof(fw_size), sizeof(checksum)); |
| checksum = be16toh(checksum); |
| |
| uint16_t expected_checksum = 0; |
| for (size_t i = sizeof(fw_size) + sizeof(checksum); i < mapped_fw.size(); i++) { |
| expected_checksum += fw_data[i]; |
| } |
| |
| if (checksum != expected_checksum) { |
| zxlogf(ERROR, "Firmware checksum doesn't match calculated value"); |
| return zx::error(ZX_ERR_IO_DATA_INTEGRITY); |
| } |
| |
| const uint8_t subsys_count = fw_data[kSubsysCountOffset]; |
| if (subsys_count > kMaxSubsysCount) { |
| zxlogf(ERROR, "Firmware subsys count is %u, only %zu are allowed", subsys_count, |
| kMaxSubsysCount); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| |
| size_t subsys_data_offset = kSubsysDataOffset; |
| for (uint32_t i = 0; i < subsys_count; i++) { |
| const uint8_t* subsys_header = fw_data + kFirmwareHeaderSize + (i * kSubsysEntrySize); |
| FirmwareSubsysInfo& entry = out_subsys_entries[i]; |
| |
| entry.type = *subsys_header++; |
| |
| memcpy(&entry.size, subsys_header, sizeof(entry.size)); |
| entry.size = be32toh(entry.size); |
| subsys_header += sizeof(entry.size); |
| |
| memcpy(&entry.flash_addr, subsys_header, sizeof(entry.flash_addr)); |
| entry.flash_addr = be16toh(entry.flash_addr); |
| |
| entry.data = fw_data + subsys_data_offset; |
| subsys_data_offset += entry.size; |
| |
| if (subsys_data_offset > mapped_fw.size()) { |
| zxlogf(ERROR, "Subsys offset %zu exceeds firmware size", subsys_data_offset); |
| return zx::error(ZX_ERR_IO_INVALID); |
| } |
| } |
| |
| return zx::ok(subsys_count); |
| } |
| |
| zx_status_t Gt6853Device::PrepareFirmwareUpdate( |
| cpp20::span<const FirmwareSubsysInfo> subsys_entries) { |
| constexpr zx::duration kResetHoldTime = zx::msec(10); |
| constexpr int kHoldSs51Tries = 20; |
| constexpr zx::duration kHoldSs51TryInterval = zx::msec(20); |
| |
| if (subsys_entries.empty()) { |
| zxlogf(ERROR, "Expected at least one firmware subsys entry"); |
| return ZX_ERR_IO_INVALID; |
| } |
| |
| zx::status<> status = Write(Register::kCpuRunFrom, kCpuRunFromFlash, sizeof(kCpuRunFromFlash)); |
| if (status.is_error()) { |
| return status.error_value(); |
| } |
| |
| reset_gpio_.ConfigOut(0); |
| zx::nanosleep(zx::deadline_after(kResetSetupTime)); |
| reset_gpio_.Write(1); |
| zx::nanosleep(zx::deadline_after(kResetHoldTime)); |
| |
| for (int i = 0; i < kHoldSs51Tries; i++) { |
| status = WriteAndCheck(Register::kCpuCtrl, &kCpuCtrlHoldSs51, sizeof(kCpuCtrlHoldSs51)); |
| if (status.is_error()) { |
| zx::nanosleep(zx::deadline_after(kHoldSs51TryInterval)); |
| } else { |
| break; |
| } |
| } |
| if (status.is_error()) { |
| zxlogf(ERROR, "Timed out waiting for CPU control register"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| const uint8_t dsp_mcu_power = 0; |
| status = WriteAndCheck(Register::kDspMcuPower, &dsp_mcu_power, sizeof(dsp_mcu_power)); |
| if (status.is_error()) { |
| zxlogf(ERROR, "Failed to enable DSP/MCU power: %d", status.error_value()); |
| return status.error_value(); |
| } |
| |
| // Disable the watchdog timer. |
| constexpr uint8_t kWatchdogDisableKey1 = 0x95; |
| constexpr uint8_t kWatchdogDisableKey2 = 0x27; |
| |
| if ((status = WriteReg8(Register::kCache, 0)).is_error()) { |
| return status.error_value(); |
| } |
| if ((status = WriteReg8(Register::kEsdKey, kWatchdogDisableKey1)).is_error()) { |
| return status.error_value(); |
| } |
| if ((status = WriteReg8(Register::kWtdTimer, 0)).is_error()) { |
| return status.error_value(); |
| } |
| if ((status = WriteReg8(Register::kEsdKey, kWatchdogDisableKey2)).is_error()) { |
| return status.error_value(); |
| } |
| |
| if ((status = WriteReg8(Register::kScramble, 0)).is_error()) { |
| return status.error_value(); |
| } |
| |
| return LoadIsp(subsys_entries[0]); |
| } |
| |
| zx_status_t Gt6853Device::LoadIsp(const FirmwareSubsysInfo& isp_info) { |
| zx::status status = WriteReg8(Register::kBankSelect, 0); |
| if (status.is_error()) { |
| return status.error_value(); |
| } |
| |
| if ((status = WriteReg8(Register::kAccessPatch0, 1)).is_error()) { |
| return status.error_value(); |
| } |
| |
| if ((status = WriteAndCheck(Register::kIspAddr, isp_info.data, isp_info.size)).is_error()) { |
| zxlogf(ERROR, "Failed to write ISP data: %d", status.error_value()); |
| return status.error_value(); |
| } |
| |
| const uint8_t disable_patch0_access = 0; |
| if ((status = WriteAndCheck(Register::kAccessPatch0, &disable_patch0_access, 1)).is_error()) { |
| zxlogf(ERROR, "Failed to write disable patch0 access: %d", status.error_value()); |
| return status.error_value(); |
| } |
| |
| const uint8_t isp_run_flag[2] = {0, 0}; |
| if ((status = Write(Register::kIspRunFlag, isp_run_flag, sizeof(isp_run_flag))).is_error()) { |
| return status.error_value(); |
| } |
| |
| if ((status = Write(Register::kCpuRunFrom, kCpuRunFromRam, sizeof(kCpuRunFromRam))).is_error()) { |
| return status.error_value(); |
| } |
| |
| if ((status = WriteReg8(Register::kCpuCtrl, 0)).is_error()) { |
| return status.error_value(); |
| } |
| |
| constexpr uint8_t kIspRunFlagWorking1 = 0xaa; |
| constexpr uint8_t kIspRunFlagWorking2 = 0xbb; |
| constexpr zx::duration kIspRunFlagTryInterval = zx::msec(10); |
| |
| for (int i = 0; i < kFirmwareTries; i++) { |
| zx::nanosleep(zx::deadline_after(kIspRunFlagTryInterval)); |
| uint8_t isp_run_check[2]; |
| if (Read(Register::kIspRunFlag, isp_run_check, sizeof(isp_run_check)).is_ok()) { |
| if (isp_run_check[0] == kIspRunFlagWorking1 && isp_run_check[1] == kIspRunFlagWorking2) { |
| return ZX_OK; |
| } |
| } |
| } |
| |
| zxlogf(ERROR, "Timed out waiting for ISP to be ready"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx_status_t Gt6853Device::FlashSubsystem(const FirmwareSubsysInfo& subsys_info) { |
| constexpr uint32_t kIspMaxTransferSize = 1024 * 4; |
| constexpr size_t kPacketHeaderAndChecksumSize = sizeof(uint16_t) * 3; |
| constexpr int kFirmwarePacketTries = 3; |
| |
| // Packet format (total size n, all fields big-endian): |
| // 0x00: data length |
| // 0x02: flash address |
| // 0x04: data |
| // ...: data |
| // n-2: checksum of data length, flash address, and data fields |
| |
| uint32_t remaining = subsys_info.size; |
| uint32_t offset = 0; |
| while (remaining > 0) { |
| const auto transfer_size = static_cast<uint16_t>(std::min(remaining, kIspMaxTransferSize)); |
| |
| uint8_t packet_buffer[kIspMaxTransferSize + kPacketHeaderAndChecksumSize]; |
| |
| const uint16_t transfer_size_be = htobe16(transfer_size); |
| memcpy(packet_buffer, &transfer_size_be, 2); |
| |
| const uint16_t flash_addr_be = htobe16(subsys_info.flash_addr + (offset >> 8)); |
| memcpy(packet_buffer + 2, &flash_addr_be, 2); |
| |
| memcpy(packet_buffer + 4, subsys_info.data + offset, transfer_size); |
| |
| const uint16_t checksum_be = htobe16(Checksum16(packet_buffer, transfer_size + 4)); |
| memcpy(packet_buffer + transfer_size + 4, &checksum_be, 2); |
| |
| zx_status_t status; |
| for (int i = 0; i < kFirmwarePacketTries; i++) { |
| status = SendFirmwarePacket(subsys_info.type, packet_buffer, |
| transfer_size + kPacketHeaderAndChecksumSize); |
| if (status == ZX_OK) { |
| break; |
| } |
| } |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Exhausted retries for sending subsys %u packet", subsys_info.type); |
| return status; |
| } |
| |
| offset += transfer_size; |
| remaining -= transfer_size; |
| } |
| |
| return ZX_OK; |
| } |
| |
| uint16_t Gt6853Device::Checksum16(const uint8_t* data, const size_t size) { |
| ZX_ASSERT(size % 2 == 0); |
| |
| uint16_t checksum = 0; |
| for (size_t i = 0; i < size; i += 2) { |
| uint16_t entry; |
| memcpy(&entry, data + i, 2); |
| checksum += be16toh(entry); |
| } |
| |
| return ~checksum + 1; |
| } |
| |
| zx_status_t Gt6853Device::SendFirmwarePacket(const uint8_t type, const uint8_t* packet, |
| const size_t size) { |
| constexpr uint8_t kFlashStatusWriting = 0xaa; |
| constexpr uint8_t kFlashStatusSuccess = 0xbb; |
| constexpr uint8_t kFlashStatusError = 0xcc; |
| constexpr uint8_t kFlashStatusCheckError = 0xdd; |
| constexpr zx::duration kFlashWritingWait = zx::msec(55); |
| |
| zx::status<> status = WriteAndCheck(Register::kIspBuffer, packet, size); |
| if (status.is_error()) { |
| zxlogf(ERROR, "Failed to send firmware packet: %d", status.error_value()); |
| return status.error_value(); |
| } |
| |
| const uint8_t flash_flag[2] = {0, 0}; |
| if ((status = WriteAndCheck(Register::kFlashFlag, flash_flag, sizeof(flash_flag))).is_error()) { |
| zxlogf(ERROR, "Failed to set flash flag: %d", status.error_value()); |
| return status.error_value(); |
| } |
| |
| const uint8_t subsys_type[2] = {type, type}; |
| status = WriteAndCheck(Register::kSubsysType, subsys_type, sizeof(subsys_type)); |
| if (status.is_error()) { |
| zxlogf(ERROR, "Failed to set subsys type to %u: %d", type, status.error_value()); |
| return status.error_value(); |
| } |
| |
| for (int i = 0; i < kFirmwareTries; i++) { |
| uint8_t flash_status[2]; |
| if ((status = Read(Register::kFlashFlag, flash_status, sizeof(flash_status))).is_error()) { |
| return status.error_value(); |
| } |
| |
| if (flash_status[0] == kFlashStatusWriting && flash_status[1] == kFlashStatusWriting) { |
| zx::nanosleep(zx::deadline_after(kFlashWritingWait)); |
| continue; |
| } |
| |
| if (flash_status[0] == kFlashStatusSuccess && flash_status[1] == kFlashStatusSuccess) { |
| if ((status = Read(Register::kFlashFlag, flash_status, sizeof(flash_status))).is_error()) { |
| return status.error_value(); |
| } |
| if (flash_status[0] == kFlashStatusSuccess && flash_status[1] == kFlashStatusSuccess) { |
| return ZX_OK; |
| } |
| } |
| |
| if (flash_status[0] == kFlashStatusError && flash_status[1] == kFlashStatusError) { |
| zxlogf(ERROR, "Failed to flash subsys %u", type); |
| return ZX_ERR_IO; |
| } |
| |
| if (flash_status[0] == kFlashStatusCheckError) { |
| zxlogf(ERROR, "Flash checksum error for subsys %u", type); |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| |
| zx::nanosleep(zx::deadline_after(zx::msec(1))); |
| } |
| |
| zxlogf(ERROR, "Timed out waiting for subsys %u flash to complete", type); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx_status_t Gt6853Device::FinishFirmwareUpdate() { |
| constexpr zx::duration kResetHoldTime = zx::msec(80); |
| |
| zx::status<> status = WriteReg8(Register::kCpuCtrl, kCpuCtrlHoldSs51); |
| if (status.is_error()) { |
| return status.error_value(); |
| } |
| |
| status = Write(Register::kCpuRunFrom, kCpuRunFromFlash, sizeof(kCpuRunFromFlash)); |
| if (status.is_error()) { |
| return status.error_value(); |
| } |
| |
| if ((status = WriteReg8(Register::kCpuCtrl, 0)).is_error()) { |
| return status.error_value(); |
| } |
| |
| reset_gpio_.Write(0); |
| zx::nanosleep(zx::deadline_after(kResetSetupTime)); |
| reset_gpio_.Write(1); |
| zx::nanosleep(zx::deadline_after(kResetHoldTime)); |
| |
| zxlogf(INFO, "Updated firmware, reset IC"); |
| firmware_status_.Set("update succeeded"); |
| return ZX_OK; |
| } |
| |
| zx::status<uint8_t> Gt6853Device::ReadReg8(const Register reg) { |
| const uint16_t address = htobe16(static_cast<uint16_t>(reg)); |
| uint8_t value = 0; |
| zx_status_t status = i2c_.WriteReadSync(reinterpret_cast<const uint8_t*>(&address), |
| sizeof(address), &value, sizeof(value)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to read from 0x%02x: %d", address, status); |
| return zx::error_status(status); |
| } |
| |
| return zx::ok(value); |
| } |
| |
| zx::status<> Gt6853Device::Read(const Register reg, uint8_t* buffer, const size_t size) { |
| uint16_t address = static_cast<uint16_t>(reg); |
| size_t remaining = size; |
| |
| while (remaining > 0) { |
| const size_t transfer_size = std::min(remaining, kI2cMaxTransferSize); |
| const uint16_t be_address = htobe16(address); |
| zx_status_t status = i2c_.WriteReadSync(reinterpret_cast<const uint8_t*>(&be_address), |
| sizeof(be_address), buffer, transfer_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to read %zu bytes from 0x%02x: %d", transfer_size, address, status); |
| return zx::error_status(status); |
| } |
| |
| address += transfer_size; |
| buffer += transfer_size; |
| remaining -= transfer_size; |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::status<> Gt6853Device::WriteReg8(const Register reg, const uint8_t value) { |
| const uint16_t address = static_cast<uint16_t>(reg); |
| const uint8_t buffer[] = { |
| static_cast<uint8_t>(address >> 8), |
| static_cast<uint8_t>(address & 0xff), |
| value, |
| }; |
| zx_status_t status = i2c_.WriteSync(buffer, sizeof(buffer)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to write 0x%02x to 0x%02x: %d", value, address, status); |
| return zx::error_status(status); |
| } |
| return zx::ok(); |
| } |
| |
| zx::status<> Gt6853Device::Write(const Register reg, const uint8_t* buffer, const size_t size) { |
| uint16_t address = static_cast<uint16_t>(reg); |
| size_t remaining = size; |
| |
| while (remaining > 0) { |
| const size_t transfer_size = std::min(remaining, kI2cMaxTransferSize - sizeof(address)); |
| const uint16_t be_address = htobe16(address); |
| |
| uint8_t write_buffer[kI2cMaxTransferSize]; |
| memcpy(write_buffer, &be_address, sizeof(be_address)); |
| memcpy(write_buffer + sizeof(be_address), buffer, transfer_size); |
| |
| zx_status_t status = i2c_.WriteSync(write_buffer, sizeof(be_address) + transfer_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to write %zu bytes to 0x%02x: %d", transfer_size, address, status); |
| return zx::error_status(status); |
| } |
| |
| address += transfer_size; |
| buffer += transfer_size; |
| remaining -= transfer_size; |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::status<> Gt6853Device::WriteAndCheck(Register reg, const uint8_t* buffer, size_t size) { |
| zx::status<> write_status = Write(reg, buffer, size); |
| if (write_status.is_error()) { |
| return write_status; |
| } |
| |
| uint16_t address = static_cast<uint16_t>(reg); |
| size_t remaining = size; |
| |
| while (remaining > 0) { |
| const size_t transfer_size = std::min(remaining, kI2cMaxTransferSize); |
| const uint16_t be_address = htobe16(address); |
| |
| uint8_t read_buffer[kI2cMaxTransferSize]; |
| zx_status_t status = i2c_.WriteReadSync(reinterpret_cast<const uint8_t*>(&be_address), |
| sizeof(be_address), read_buffer, transfer_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to read %zu bytes from 0x%02x: %d", transfer_size, address, status); |
| return zx::error_status(status); |
| } |
| |
| if (memcmp(read_buffer, buffer, transfer_size) != 0) { |
| return zx::error_status(ZX_ERR_IO_DATA_INTEGRITY); |
| } |
| |
| address += transfer_size; |
| buffer += transfer_size; |
| remaining -= transfer_size; |
| } |
| |
| return zx::ok(); |
| } |
| |
| int Gt6853Device::Thread() { |
| zx::time timestamp; |
| while (interrupt_.wait(×tamp) == ZX_OK) { |
| zx::status<uint8_t> status = ReadReg8(Register::kEventStatusReg); |
| if (status.is_error()) { |
| zxlogf(ERROR, "Failed to read event status register"); |
| return thrd_error; |
| } |
| if (status.value() != kTouchEvent) { |
| continue; |
| } |
| |
| if ((status = ReadReg8(Register::kContactsReg)).is_error()) { |
| zxlogf(ERROR, "Failed to read contact count register"); |
| return thrd_error; |
| } |
| |
| const uint8_t contacts = status.value() & 0b1111; |
| if (contacts > kMaxContacts) { |
| zxlogf(ERROR, "Touch event with too many contacts: %u", contacts); |
| return thrd_error; |
| } |
| |
| uint8_t contacts_buffer[kContactSize * kMaxContacts] = {}; |
| if (Read(Register::kContactsStartReg, contacts_buffer, contacts * kContactSize).is_error()) { |
| zxlogf(ERROR, "Failed to read contacts"); |
| return thrd_error; |
| } |
| |
| // Clear the status register so that interrupts stop being generated. |
| if (WriteReg8(Register::kEventStatusReg, 0).is_error()) { |
| zxlogf(ERROR, "Failed to reset event status register"); |
| return thrd_error; |
| } |
| |
| Gt6853InputReport report = { |
| .event_time = timestamp, |
| .contacts = {}, |
| .num_contacts = contacts, |
| }; |
| for (uint8_t i = 0; i < contacts; i++) { |
| report.contacts[i] = ParseContact(&contacts_buffer[i * kContactSize]); |
| } |
| |
| input_report_readers_.SendReportToAllReaders(report); |
| |
| const zx::duration latency = zx::clock::get_monotonic() - timestamp; |
| |
| total_latency_ += latency; |
| report_count_++; |
| average_latency_usecs_.Set(total_latency_.to_usecs() / report_count_); |
| |
| if (latency > max_latency_) { |
| max_latency_ = latency; |
| max_latency_usecs_.Set(max_latency_.to_usecs()); |
| } |
| } |
| |
| return thrd_success; |
| } |
| |
| void Gt6853Device::Shutdown() { |
| interrupt_.destroy(); |
| thrd_join(thread_, nullptr); |
| } |
| |
| static zx_driver_ops_t gt6853_driver_ops = []() -> zx_driver_ops_t { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = Gt6853Device::Create; |
| return ops; |
| }(); |
| |
| } // namespace touch |
| |
| ZIRCON_DRIVER(Gt6853Device, touch::gt6853_driver_ops, "zircon", "0.1"); |