blob: 51cfc0b8dc4cc9fd82c2472f2b17d77ba1d12510 [file] [log] [blame]
// 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) 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();
}
} // namespace ft