blob: d7e12a553859f03701f2bdeeded3227a660b9774 [file]
// 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_device.h"
#include <lib/ddk/metadata.h>
#include <lib/device-protocol/display-panel.h>
#include <lib/driver/compat/cpp/device_server.h>
#include <lib/driver/fake-platform-device/cpp/fake-pdev.h>
#include <lib/driver/testing/cpp/driver_test.h>
#include <lib/fake-i2c/fake-i2c.h>
#include <lib/zx/clock.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <cstddef>
#include <gtest/gtest.h>
#include "ft_firmware.h"
#include "src/devices/gpio/testing/fake-gpio/fake-gpio.h"
#include "src/lib/testing/predicates/status.h"
namespace {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc99-designator"
// Firmware must be at least 0x120 bytes. Add some extra size to make them different.
constexpr uint8_t kFirmware0[0x120 + 0] = {0x00, 0xd2, 0xc8, 0x53, [0x10a] = 0xd5};
constexpr uint8_t kFirmware1[0x120 + 1] = {0x10, 0x58, 0xb2, 0x12, [0x10a] = 0xc8};
constexpr uint8_t kFirmware2[0x120 + 2] = {0xb7, 0xf9, 0xd1, 0x12, [0x10a] = 0xb0};
constexpr uint8_t kFirmware3[0x120 + 3] = {0x02, 0x69, 0x96, 0x71, [0x10a] = 0x61};
#pragma GCC diagnostic pop
} // namespace
namespace ft {
namespace fi2c = fuchsia_hardware_i2c;
namespace fgpio = fuchsia_hardware_gpio;
const FirmwareEntry kFirmwareEntries[] = {
{
.panel_type = display::PanelType::kInnoluxP101dezFitipowerJd9364,
.firmware_data = kFirmware0,
.firmware_size = sizeof(kFirmware0),
},
{
.panel_type = display::PanelType::kBoeTv101wxmFitipowerJd9364,
.firmware_data = kFirmware1,
.firmware_size = sizeof(kFirmware1),
},
{
.panel_type = display::PanelType::kBoeTv101wxmFitipowerJd9365,
.firmware_data = kFirmware2,
.firmware_size = sizeof(kFirmware2),
},
{
.panel_type = display::PanelType::kBoeTv070wsmFitipowerJd9364Astro,
.firmware_data = kFirmware3,
.firmware_size = sizeof(kFirmware3),
},
};
const size_t kNumFirmwareEntries = std::size(kFirmwareEntries);
class FakeFtDevice : public fake_i2c::FakeI2c {
public:
~FakeFtDevice() { ZX_ASSERT(expected_report_.empty()); }
uint32_t firmware_write_size() const { return firmware_write_size_; }
void ExpectReport(uint8_t addr, std::vector<uint8_t> report) {
expected_report_.emplace(addr, std::move(report));
}
protected:
zx_status_t Transact(const uint8_t* write_buffer, size_t write_buffer_size, uint8_t* read_buffer,
size_t* read_buffer_size, size_t expected_read_size) override {
if (write_buffer_size < 1) {
return ZX_ERR_IO;
}
*read_buffer_size = 0;
if (write_buffer[0] == 0xa3) { // Chip core register
read_buffer[0] = 0x58; // Firmware is valid
*read_buffer_size = 1;
} else if (write_buffer[0] == 0xa6) { // Chip firmware version
read_buffer[0] = kFirmware1[0x10a]; // Set to a known version to test the up-to-date case
*read_buffer_size = 1;
} else if (write_buffer[0] == 0xfc && write_buffer_size == 2) { // Chip work mode
if (write_buffer[1] != 0xaa && write_buffer[1] != 0x55) { // Soft reset
return ZX_ERR_IO;
}
} else if (write_buffer[0] == 0xeb && write_buffer_size == 3) { // HID to STD
if (write_buffer[1] != 0xaa || write_buffer[2] != 0x09) {
return ZX_ERR_IO;
}
} else if (write_buffer[0] == 0x55 && write_buffer_size == 1) { // Unlock boot
} else if (write_buffer[0] == 0x90 && write_buffer_size == 1) { // Boot ID
read_buffer[0] = 0x58;
read_buffer[1] = 0x2c;
*read_buffer_size = 2;
} else if (write_buffer[0] == 0x09 && write_buffer_size == 2) { // Flash erase
if (write_buffer[1] != 0x0b) { // Erase app area
return ZX_ERR_IO;
}
} else if (write_buffer[0] == 0xb0 && write_buffer_size == 4) { // Set erase size
} else if (write_buffer[0] == 0x61 && write_buffer_size == 1) { // Start erase
ecc_ = 0;
flash_status_ = 0xf0aa;
} else if (write_buffer[0] == 0x6a && write_buffer_size == 1) { // Read flash status
read_buffer[0] = flash_status_ >> 8;
read_buffer[1] = flash_status_ & 0xff;
*read_buffer_size = 2;
} else if (write_buffer[0] == 0xbf && write_buffer_size >= 6) { // Firmware packet
const uint32_t address = (write_buffer[1] << 16) | (write_buffer[2] << 8) | write_buffer[3];
const auto packet_size = static_cast<uint8_t>((write_buffer[4] << 8) | write_buffer[5]);
if ((packet_size + 6) != write_buffer_size) {
return ZX_ERR_IO;
}
for (uint32_t i = 6; i < write_buffer_size; i++) {
ecc_ ^= write_buffer[i];
}
flash_status_ = (0x1000 + (address / packet_size)) & 0xffff;
firmware_write_size_ += packet_size; // Ignore overlapping addresses.
} else if (write_buffer[0] == 0x64 && write_buffer_size == 1) { // ECC initialization
} else if (write_buffer[0] == 0x65 && write_buffer_size == 6) { // Start ECC calculation
flash_status_ = 0xf055; // ECC calculation done
} else if (write_buffer[0] == 0x66 && write_buffer_size == 1) { // Read calculated ECC
read_buffer[0] = ecc_;
*read_buffer_size = 1;
} else if (write_buffer[0] == 0x07 && write_buffer_size == 1) { // Reset
} else if ((write_buffer[0] == FTS_REG_TYPE || write_buffer[0] == FTS_REG_FIRMID ||
write_buffer[0] == FTS_REG_VENDOR_ID || write_buffer[0] == FTS_REG_PANEL_ID ||
write_buffer[0] == FTS_REG_RELEASE_ID_HIGH ||
write_buffer[0] == FTS_REG_RELEASE_ID_LOW ||
write_buffer[0] == FTS_REG_IC_VERSION) &&
write_buffer_size == 1) { // LogRegisterValue
read_buffer[0] = 0;
*read_buffer_size = 1;
} else if (write_buffer_size == 1) { // Read report
EXPECT_FALSE(expected_report_.empty());
EXPECT_EQ(write_buffer[0], expected_report_.front().first);
memcpy(read_buffer, expected_report_.front().second.data(),
expected_report_.front().second.size());
*read_buffer_size = expected_report_.front().second.size();
expected_report_.pop();
}
return ZX_OK;
}
private:
uint16_t flash_status_ = 0;
uint8_t ecc_ = 0;
uint32_t firmware_write_size_ = 0;
std::queue<std::pair<uint8_t, std::vector<uint8_t>>> expected_report_; // address, data pair
};
class FtDeviceTestEnvironment : public fdf_testing::Environment {
public:
void Init(zx::interrupt interrupt, const fuchsia_hardware_input_focaltech::Metadata& metadata,
const std::optional<display::PanelType>& panel_type) {
interrupt_gpio_.SetInterrupt(zx::ok(std::move(interrupt)));
pdev_.AddFidlMetadata(fuchsia_hardware_input_focaltech::Metadata::kSerializableName, metadata);
device_server_.Initialize("pdev", std::nullopt, {});
if (panel_type.has_value()) {
device_server_.AddMetadata(DEVICE_METADATA_DISPLAY_PANEL_TYPE, &panel_type.value(),
sizeof(panel_type.value()));
}
}
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
async_dispatcher_t* dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher();
EXPECT_OK(device_server_.Serve(dispatcher, &to_driver_vfs));
EXPECT_OK(
to_driver_vfs.AddService<fi2c::Service>(i2c_.CreateInstanceHandler(dispatcher), "i2c"));
EXPECT_OK(to_driver_vfs.AddService<fgpio::Service>(reset_gpio_.CreateInstanceHandler(),
"gpio-reset"));
EXPECT_OK(to_driver_vfs.AddService<fgpio::Service>(interrupt_gpio_.CreateInstanceHandler(),
"gpio-int"));
EXPECT_OK(to_driver_vfs.AddService<fuchsia_hardware_platform_device::Service>(
pdev_.GetInstanceHandler(dispatcher), "pdev"));
return zx::ok();
}
FakeFtDevice& i2c() { return i2c_; }
fake_gpio::FakeGpio& interrupt_gpio() { return interrupt_gpio_; }
fake_gpio::FakeGpio& reset_gpio() { return reset_gpio_; }
private:
FakeFtDevice i2c_;
fake_gpio::FakeGpio interrupt_gpio_;
fake_gpio::FakeGpio reset_gpio_;
compat::DeviceServer device_server_;
fdf_fake::FakePDev pdev_;
};
class FixtureConfig final {
public:
using DriverType = FtDevice;
using EnvironmentType = FtDeviceTestEnvironment;
};
class FocaltechTest : public testing::Test {
public:
void TearDown() override { ASSERT_OK(driver_test_.StopDriver()); }
protected:
void StartDriver(const fuchsia_hardware_input_focaltech::Metadata& metadata,
const std::optional<display::PanelType>& panel_type = std::nullopt) {
zx::interrupt interrupt;
ASSERT_EQ(ZX_OK, zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &interrupt));
ASSERT_EQ(ZX_OK, interrupt.duplicate(ZX_RIGHT_SAME_RIGHTS, &interrupt_));
driver_test_.RunInEnvironmentTypeContext([interrupt = std::move(interrupt), &metadata,
&panel_type](FtDeviceTestEnvironment& env) mutable {
env.Init(std::move(interrupt), metadata, panel_type);
});
ASSERT_OK(driver_test_.StartDriver());
VerifyGpioInit();
zx::result input_device = driver_test_.ConnectThroughDevfs<fuchsia_input_report::InputDevice>(
FtDevice::kChildNodeName);
ASSERT_OK(input_device);
input_device_.Bind(std::move(input_device.value()),
driver_test_.runtime().GetForegroundDispatcher()->async_dispatcher());
}
fidl::WireClient<fuchsia_input_report::InputDevice>& input_device() { return input_device_; }
zx::interrupt& interrupt() { return interrupt_; }
fdf_testing::ForegroundDriverTest<FixtureConfig>& driver_test() { return driver_test_; }
private:
void VerifyGpioInit() {
driver_test_.RunInEnvironmentTypeContext([](FtDeviceTestEnvironment& env) {
std::vector interrupt_states = env.interrupt_gpio().GetStateLog();
ASSERT_GE(interrupt_states.size(), size_t(1));
ASSERT_EQ(fake_gpio::ReadSubState{}, interrupt_states[0].sub_state);
std::vector reset_states = env.reset_gpio().GetStateLog();
ASSERT_GE(reset_states.size(), size_t(2));
ASSERT_EQ(fake_gpio::WriteSubState{.value = 0}, reset_states[0].sub_state);
ASSERT_EQ(fake_gpio::WriteSubState{.value = 1}, reset_states[1].sub_state);
});
}
zx::interrupt interrupt_;
fidl::WireClient<fuchsia_input_report::InputDevice> input_device_;
// Use `ForegroundDriverTest` instead of `BackgroundDriverTest` because some tests send one-way
// FIDL requests to the driver and wait for the request to be handled by the driver. This can be
// done with `runtime().RunUntilIdle()` if the driver is running on the foreground dispatcher.
fdf_testing::ForegroundDriverTest<FixtureConfig> driver_test_;
};
void VerifyDescriptor(const fuchsia_input_report::wire::DeviceDescriptor& descriptor, int64_t x_max,
int64_t y_max) {
EXPECT_TRUE(descriptor.has_device_information());
EXPECT_EQ(descriptor.device_information().vendor_id(),
static_cast<uint32_t>(fuchsia_input_report::wire::VendorId::kGoogle));
EXPECT_EQ(descriptor.device_information().product_id(),
static_cast<uint32_t>(
fuchsia_input_report::wire::VendorGoogleProductId::kFocaltechTouchscreen));
EXPECT_TRUE(descriptor.has_touch());
EXPECT_FALSE(descriptor.has_consumer_control());
EXPECT_FALSE(descriptor.has_keyboard());
EXPECT_FALSE(descriptor.has_mouse());
EXPECT_FALSE(descriptor.has_sensor());
EXPECT_TRUE(descriptor.touch().has_input());
EXPECT_FALSE(descriptor.touch().has_feature());
EXPECT_TRUE(descriptor.touch().input().has_touch_type());
EXPECT_EQ(descriptor.touch().input().touch_type(),
fuchsia_input_report::wire::TouchType::kTouchscreen);
EXPECT_TRUE(descriptor.touch().input().has_max_contacts());
EXPECT_EQ(descriptor.touch().input().max_contacts(), uint32_t(10));
EXPECT_FALSE(descriptor.touch().input().has_buttons());
EXPECT_TRUE(descriptor.touch().input().has_contacts());
EXPECT_EQ(descriptor.touch().input().contacts().size(), size_t(10));
for (const auto& c : descriptor.touch().input().contacts()) {
EXPECT_TRUE(c.has_position_x());
EXPECT_TRUE(c.has_position_y());
EXPECT_FALSE(c.has_contact_height());
EXPECT_FALSE(c.has_contact_width());
EXPECT_FALSE(c.has_pressure());
EXPECT_EQ(c.position_x().range.min, 0);
EXPECT_EQ(c.position_x().range.max, x_max);
EXPECT_EQ(c.position_x().unit.type, fuchsia_input_report::wire::UnitType::kOther);
EXPECT_EQ(c.position_x().unit.exponent, 0);
EXPECT_EQ(c.position_y().range.min, 0);
EXPECT_EQ(c.position_y().range.max, y_max);
EXPECT_EQ(c.position_y().unit.type, fuchsia_input_report::wire::UnitType::kOther);
EXPECT_EQ(c.position_y().unit.exponent, 0);
}
}
TEST_F(FocaltechTest, Metadata3x27) {
static const fuchsia_hardware_input_focaltech::Metadata kFt3x27Metadata({
.device_id = fuchsia_hardware_input_focaltech::DeviceId::kFt3X27,
.needs_firmware = false,
});
StartDriver(kFt3x27Metadata);
input_device()->GetDescriptor().ThenExactlyOnce(
[](fidl::WireUnownedResult<fuchsia_input_report::InputDevice::GetDescriptor>& result) {
ASSERT_OK(result.status());
VerifyDescriptor(result->descriptor, 600, 1024);
});
driver_test().runtime().RunUntilIdle();
}
TEST_F(FocaltechTest, Metadata5726) {
static const fuchsia_hardware_input_focaltech::Metadata kFt5726Metadata({
.device_id = fuchsia_hardware_input_focaltech::DeviceId::kFt5726,
.needs_firmware = false,
});
StartDriver(kFt5726Metadata);
input_device()->GetDescriptor().ThenExactlyOnce(
[](fidl::WireUnownedResult<fuchsia_input_report::InputDevice::GetDescriptor>& result) {
EXPECT_TRUE(result.ok());
VerifyDescriptor(result->descriptor, 800, 1280);
});
driver_test().runtime().RunUntilIdle();
}
TEST_F(FocaltechTest, Metadata6336) {
static const fuchsia_hardware_input_focaltech::Metadata kFt6336Metadata({
.device_id = fuchsia_hardware_input_focaltech::DeviceId::kFt6336,
.needs_firmware = false,
});
StartDriver(kFt6336Metadata);
input_device()->GetDescriptor().ThenExactlyOnce(
[](fidl::WireUnownedResult<fuchsia_input_report::InputDevice::GetDescriptor>& result) {
EXPECT_TRUE(result.ok());
VerifyDescriptor(result->descriptor, 480, 800);
});
driver_test().runtime().RunUntilIdle();
}
TEST_F(FocaltechTest, Firmware5726) {
static const fuchsia_hardware_input_focaltech::Metadata kFt5726Metadata({
.device_id = fuchsia_hardware_input_focaltech::DeviceId::kFt5726,
.needs_firmware = true,
});
static constexpr display::PanelType kPanelType = display::PanelType::kBoeTv101wxmFitipowerJd9365;
StartDriver(kFt5726Metadata, kPanelType);
driver_test().RunInEnvironmentTypeContext([](FtDeviceTestEnvironment& env) {
EXPECT_EQ(env.i2c().firmware_write_size(), sizeof(kFirmware2));
});
}
TEST_F(FocaltechTest, Firmware5726UpToDate) {
static const fuchsia_hardware_input_focaltech::Metadata kFt5726Metadata({
.device_id = fuchsia_hardware_input_focaltech::DeviceId::kFt5726,
.needs_firmware = true,
});
constexpr display::PanelType kPanelType = display::PanelType::kBoeTv101wxmFitipowerJd9364;
StartDriver(kFt5726Metadata, kPanelType);
driver_test().RunInEnvironmentTypeContext(
[](FtDeviceTestEnvironment& env) { EXPECT_EQ(env.i2c().firmware_write_size(), 0u); });
}
TEST_F(FocaltechTest, Touch) {
static const fuchsia_hardware_input_focaltech::Metadata kFt6336Metadata({
.device_id = fuchsia_hardware_input_focaltech::DeviceId::kFt6336,
.needs_firmware = false,
});
StartDriver(kFt6336Metadata);
auto reader_endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create();
fidl::OneWayStatus status =
input_device()->GetInputReportsReader(std::move(reader_endpoints.server));
ASSERT_EQ(ZX_OK, status.status());
fidl::WireClient<fuchsia_input_report::InputReportsReader> reader(
std::move(reader_endpoints.client),
driver_test().runtime().GetForegroundDispatcher()->async_dispatcher());
// Wait for the driver to receive the `GetInputReportsReader()` request and create an
// input-reports reader.
driver_test().runtime().RunUntilIdle();
// clang-format off
static const std::array<uint8_t, 61> kExpectedReport = {
0x02, // contact_count
// Contact 0, finger_id = 0
0x80, 0x01, // x = 0x001
0x00, 0x13, // y = 0x013
0x00, 0x00,
// Contact 1, finger_id = 1
0x80, 0x31, // x = 0x031
0x10, 0x00, // y = 0x000
0x00, 0x00,
// Contact 2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Contact 3
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Contact 4
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Contact 5
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Contact 6
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Contact 7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Contact 8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Contact 9
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// clang-format on
driver_test().RunInEnvironmentTypeContext([](FtDeviceTestEnvironment& env) {
for (size_t i = 0; i < sizeof(kExpectedReport); i += 8) {
env.i2c().ExpectReport(
static_cast<uint8_t>(i + FTS_REG_CURPOINT),
std::vector<uint8_t>(kExpectedReport.begin() + i,
kExpectedReport.begin() + std::min(i + 8, sizeof(kExpectedReport))));
}
});
interrupt().trigger(0, zx::clock::get_boot());
reader->ReadInputReports().ThenExactlyOnce(
[](fidl::WireUnownedResult<fuchsia_input_report::InputReportsReader::ReadInputReports>&
result) {
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().is_error());
const fidl::VectorView<::fuchsia_input_report::wire::InputReport>& reports =
result.value().value()->reports;
ASSERT_EQ(size_t(1), reports.size());
const fuchsia_input_report::wire::InputReport& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_touch());
const fuchsia_input_report::wire::TouchInputReport& touch_report = report.touch();
ASSERT_TRUE(touch_report.has_contacts());
ASSERT_EQ(touch_report.contacts().size(), size_t(2));
EXPECT_EQ(touch_report.contacts()[0].contact_id(), uint32_t(0));
EXPECT_EQ(touch_report.contacts()[0].position_x(), 0x001);
EXPECT_EQ(touch_report.contacts()[0].position_y(), 0x013);
EXPECT_EQ(touch_report.contacts()[1].contact_id(), uint32_t(1));
EXPECT_EQ(touch_report.contacts()[1].position_x(), 0x031);
EXPECT_EQ(touch_report.contacts()[1].position_y(), 0x000);
});
driver_test().runtime().RunUntilIdle();
}
TEST_F(FocaltechTest, TouchWithGap) {
static const fuchsia_hardware_input_focaltech::Metadata kFt6336Metadata({
.device_id = fuchsia_hardware_input_focaltech::DeviceId::kFt6336,
.needs_firmware = false,
});
StartDriver(kFt6336Metadata);
auto reader_endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create();
fidl::OneWayStatus status =
input_device()->GetInputReportsReader(std::move(reader_endpoints.server));
ASSERT_EQ(ZX_OK, status.status());
fidl::WireClient<fuchsia_input_report::InputReportsReader> reader(
std::move(reader_endpoints.client),
driver_test().runtime().GetForegroundDispatcher()->async_dispatcher());
driver_test().runtime().RunUntilIdle();
// clang-format off
static const std::array<uint8_t, 61> kExpectedReport = {
0x02, // contact_count reported by hardware (slots 0 and 1)
// Contact 0 (UP event = 0x40), should be ignored
0x40, 0x01, // x = 0x001
0x00, 0x13, // y = 0x013
0x00, 0x00,
// Contact 1 (CONTACT event = 0x80), finger_id = 1
0x80, 0x31, // x = 0x031
0x10, 0x00, // y = 0x000
0x00, 0x00,
// Remaining slots empty
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// clang-format on
driver_test().RunInEnvironmentTypeContext([](FtDeviceTestEnvironment& env) {
for (size_t i = 0; i < sizeof(kExpectedReport); i += 8) {
env.i2c().ExpectReport(
static_cast<uint8_t>(i + FTS_REG_CURPOINT),
std::vector<uint8_t>(kExpectedReport.begin() + i,
kExpectedReport.begin() + std::min(i + 8, sizeof(kExpectedReport))));
}
});
interrupt().trigger(0, zx::clock::get_boot());
reader->ReadInputReports().ThenExactlyOnce(
[](fidl::WireUnownedResult<fuchsia_input_report::InputReportsReader::ReadInputReports>&
result) {
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().is_error());
const fidl::VectorView<::fuchsia_input_report::wire::InputReport>& reports =
result.value().value()->reports;
ASSERT_EQ(size_t(1), reports.size());
const fuchsia_input_report::wire::InputReport& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_touch());
const fuchsia_input_report::wire::TouchInputReport& touch_report = report.touch();
ASSERT_TRUE(touch_report.has_contacts());
ASSERT_EQ(touch_report.contacts().size(), size_t(1));
EXPECT_EQ(touch_report.contacts()[0].contact_id(), uint32_t(1));
EXPECT_EQ(touch_report.contacts()[0].position_x(), 0x031);
EXPECT_EQ(touch_report.contacts()[0].position_y(), 0x000);
});
driver_test().runtime().RunUntilIdle();
}
TEST(TouchRecordTest, XYDecode) {
TouchRecord record;
record.x_high = 0x01; // event_flag 0 (DOWN), x_high 1
record.x_low = 0x23; // x_low 0x23
record.y_high = 0x74; // touch_id 7, y_high 4
record.y_low = 0x56; // y_low 0x56
// x = 0x123 (high bits [11:8] 0x01, low bits [7:0] 0x23)
// y = 0x456 (high bits [11:8] 0x04, low bits [7:0] 0x56)
// finger_id = 7
EXPECT_EQ(record.x(), 0x123);
EXPECT_EQ(record.y(), 0x456);
EXPECT_EQ(record.finger_id(), 7);
EXPECT_EQ(record.event_type(), FtTouchEventType::kDown);
}
} // namespace ft