blob: 476bf5f1431020ffcbd4c58119437bd247822e3c [file] [log] [blame] [edit]
// Copyright 2018 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/driver/compat/cpp/metadata.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/platform-device/cpp/pdev.h>
#include <lib/fit/defer.h>
#include <lib/zx/clock.h>
#include <lib/zx/process.h>
#include <lib/zx/profile.h>
#include <lib/zx/time.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <zircon/compiler.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <algorithm>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include "lib/device-protocol/display-panel.h"
#include "src/devices/i2c/lib/i2c-channel/i2c-channel.h"
namespace ft {
namespace {
constexpr fuchsia_input_report::wire::Axis XYAxis(int64_t max) {
return {
.range = {.min = 0, .max = max},
.unit =
{
.type = fuchsia_input_report::wire::UnitType::kOther,
.exponent = 0,
},
};
}
constexpr size_t kFt3x27XMax = 600;
constexpr size_t kFt3x27YMax = 1024;
constexpr size_t kFt6336XMax = 480;
constexpr size_t kFt6336YMax = 800;
constexpr size_t kFt5726XMax = 800;
constexpr size_t kFt5726YMax = 1280;
constexpr size_t kFt5336XMax = 1080;
constexpr size_t kFt5336YMax = 1920;
constexpr uint8_t kFtTouchEventTypeMask = 0xC0;
constexpr uint8_t kFtTouchEventTypeShift = 6;
enum FtTouchEventType : uint8_t {
DOWN = 0,
UP = 1,
CONTACT = 2,
};
} // namespace
void FtDevice::FtInputReport::ToFidlInputReport(
fidl::WireTableBuilder<::fuchsia_input_report::wire::InputReport>& input_report,
fidl::AnyArena& allocator) const {
fidl::VectorView<fuchsia_input_report::wire::ContactInputReport> contact_rpt(allocator,
contact_count);
for (size_t i = 0; i < contact_count; i++) {
contact_rpt[i] = fuchsia_input_report::wire::ContactInputReport::Builder(allocator)
.contact_id(contacts[i].finger_id)
.position_x(contacts[i].x)
.position_y(contacts[i].y)
.Build();
}
auto touch_report =
fuchsia_input_report::wire::TouchInputReport::Builder(allocator).contacts(contact_rpt);
input_report.event_time(event_time.get()).touch(touch_report.Build());
}
FtDevice::FtInputReport FtDevice::ParseReport(std::span<const uint8_t> buf) {
FtInputReport report;
const uint8_t contact_count = std::min(buf[0], static_cast<uint8_t>(report.contacts.max_size()));
report.contact_count = 0;
for (size_t i = 0; i < contact_count; i++) {
const std::span touch_record = buf.subspan(1 + (i * kFingerRptSize), 3);
if (((touch_record[0] & kFtTouchEventTypeMask) >> kFtTouchEventTypeShift) !=
FtTouchEventType::CONTACT) {
continue;
}
report.contacts[i].x = static_cast<uint16_t>(((touch_record[0] & 0x0f) << 8) + touch_record[1]);
report.contacts[i].y = static_cast<uint16_t>(((touch_record[2] & 0x0f) << 8) + touch_record[3]);
report.contacts[i].finger_id = static_cast<uint8_t>(touch_record[2] >> 4);
report.contact_count++;
}
return report;
}
void FtDevice::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt) {
if (status != ZX_OK) {
if (status != ZX_ERR_CANCELED) {
fdf::error("Interrupt error: {}", zx_status_get_string(status));
}
return;
}
TRACE_DURATION("input", "FtDevice Read");
std::array<uint8_t, (kMaxPoints * kFingerRptSize) + 1> read_buf;
zx::result result = Read(FTS_REG_CURPOINT, read_buf);
if (result.is_ok()) {
auto timestamp = zx::time(interrupt->timestamp);
auto report = ParseReport(read_buf);
report.event_time = timestamp;
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());
}
if (read_buf[0] > 0) {
total_report_count_.Add(1);
last_event_timestamp_.Set(timestamp.get());
}
} else {
fdf::error("Failed to read i2c: {}", result);
}
irq_.ack();
}
zx::result<> FtDevice::Start() {
zx::result i2c = i2c::I2cChannel::FromIncoming(*incoming(), "i2c");
if (i2c.is_error()) {
fdf::error("Failed to connect to i2c: {}", i2c);
return i2c.take_error();
}
i2c_ = std::move(i2c.value());
zx::result int_gpio_client =
incoming()->Connect<fuchsia_hardware_gpio::Service::Device>("gpio-int");
if (int_gpio_client.is_error()) {
fdf::error("Failed to connect to interrupt gpio: {}", int_gpio_client);
return zx::error(ZX_ERR_NO_RESOURCES);
}
int_gpio_.Bind(std::move(int_gpio_client.value()));
zx::result reset_gpio_client =
incoming()->Connect<fuchsia_hardware_gpio::Service::Device>("gpio-reset");
if (reset_gpio_client.is_error()) {
fdf::error("Failed to connect to reset gpio: {}", reset_gpio_client);
return zx::error(ZX_ERR_NO_RESOURCES);
}
reset_gpio_.Bind(std::move(reset_gpio_client.value()));
{
fidl::WireResult result = int_gpio_->SetBufferMode(fuchsia_hardware_gpio::BufferMode::kInput);
if (!result.ok()) {
fdf::error("Failed to send SetBufferMode request to int gpio: {}", result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
fdf::error("Failed to configure int gpio to input: {}",
zx_status_get_string(result->error_value()));
return result->take_error();
}
}
{
fidl::Arena arena;
auto config = fuchsia_hardware_gpio::wire::InterruptConfiguration::Builder(arena)
.mode(fuchsia_hardware_gpio::InterruptMode::kEdgeLow)
.Build();
fidl::WireResult result = int_gpio_->ConfigureInterrupt(config);
if (!result.ok()) {
fdf::error("Failed to send ConfigureInterrupt request to int gpio: {}",
result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
fdf::error("Failed to configure int gpio: {}", zx_status_get_string(result->error_value()));
return result->take_error();
}
}
fidl::WireResult interrupt = int_gpio_->GetInterrupt({});
if (!interrupt.ok()) {
fdf::error("Failed to send GetInterrupt request to int gpio: {}", interrupt.status_string());
return zx::error(interrupt.status());
}
if (interrupt->is_error()) {
fdf::error("Failed to get interrupt from int gpio: {}",
zx_status_get_string(interrupt->error_value()));
return interrupt->take_error();
}
irq_ = std::move(interrupt.value()->interrupt);
irq_handler_.set_object(irq_.get());
irq_handler_.Begin(dispatcher());
zx::result pdev_client =
incoming()->Connect<fuchsia_hardware_platform_device::Service::Device>("pdev");
if (pdev_client.is_error()) {
fdf::error("Failed to connect to platform device: {}", pdev_client);
return pdev_client.take_error();
}
fdf::PDev pdev(std::move(pdev_client.value()));
zx::result metadata = pdev.GetFidlMetadata<fuchsia_hardware_input_focaltech::Metadata>();
if (metadata.is_error()) {
fdf::error("Failed to get metadata: {}", metadata);
return metadata.take_error();
}
const fuchsia_hardware_input_focaltech::Metadata& device_info = metadata.value();
if (!device_info.device_id().has_value()) {
fdf::error("Metadata missing `device_id` field");
return zx::error(ZX_ERR_INTERNAL);
}
if (!device_info.needs_firmware().has_value()) {
fdf::error("Metadata missing `needs_firmware` field");
return zx::error(ZX_ERR_INTERNAL);
}
display::PanelType panel_type = display::PanelType::kUnknown;
zx::result panel_type_result = compat::GetMetadata<display::PanelType>(
incoming(), DEVICE_METADATA_DISPLAY_PANEL_TYPE, "pdev");
if (panel_type_result.is_ok()) {
panel_type = *panel_type_result.value();
} else if (panel_type_result.status_value() == ZX_ERR_NOT_FOUND) {
fdf::warn("Display panel type information not found.");
} else {
fdf::error("Failed to get panel type: {}", panel_type_result);
return panel_type_result.take_error();
}
fuchsia_hardware_input_focaltech::DeviceId device_id = device_info.device_id().value();
switch (device_id) {
case fuchsia_hardware_input_focaltech::DeviceId::kFt3X27:
x_max_ = kFt3x27XMax;
y_max_ = kFt3x27YMax;
break;
case fuchsia_hardware_input_focaltech::DeviceId::kFt6336:
x_max_ = kFt6336XMax;
y_max_ = kFt6336YMax;
break;
case fuchsia_hardware_input_focaltech::DeviceId::kFt5726:
x_max_ = kFt5726XMax;
y_max_ = kFt5726YMax;
break;
case fuchsia_hardware_input_focaltech::DeviceId::kFt5336:
// Currently we assume the panel to be always Khadas TS050. If this changes,
// we may need extra information from the metadata to determine which HID
// report descriptor to use.
x_max_ = kFt5336XMax;
y_max_ = kFt5336YMax;
break;
default:
fdf::error("Unkown device ID {}", static_cast<uint32_t>(device_id));
return zx::error(ZX_ERR_INTERNAL);
}
// Reset the chip -- should be low for at least 1ms, and the chip should take at most 200ms to
// initialize.
{
fidl::WireResult result =
reset_gpio_->SetBufferMode(fuchsia_hardware_gpio::BufferMode::kOutputLow);
if (!result.ok()) {
fdf::error("Failed to send SetBufferMode request to reset gpio: {}", result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
fdf::error("Failed to configure reset gpio to output: {}",
zx_status_get_string(result->error_value()));
return result->take_error();
}
}
zx::nanosleep(zx::deadline_after(zx::msec(5)));
{
fidl::WireResult result =
reset_gpio_->SetBufferMode(fuchsia_hardware_gpio::BufferMode::kOutputHigh);
if (!result.ok()) {
fdf::error("Failed to send Write request to reset gpio: {}", result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
fdf::error("Failed to write to reset gpio: {}", zx_status_get_string(result->error_value()));
return result->take_error();
}
}
zx::nanosleep(zx::deadline_after(zx::msec(200)));
zx_status_t status = UpdateFirmwareIfNeeded(device_info, panel_type);
if (status != ZX_OK) {
fdf::error("Failed to update firmware: {}", zx_status_get_string(status));
return zx::error(status);
}
node_ = inspector_.GetRoot().CreateChild("Chip_info");
LogRegisterValue(FTS_REG_TYPE, "TYPE");
LogRegisterValue(FTS_REG_FIRMID, "FIRMID");
LogRegisterValue(FTS_REG_VENDOR_ID, "VENDOR_ID");
LogRegisterValue(FTS_REG_PANEL_ID, "PANEL_ID");
LogRegisterValue(FTS_REG_RELEASE_ID_HIGH, "RELEASE_ID_HIGH");
LogRegisterValue(FTS_REG_RELEASE_ID_LOW, "RELEASE_ID_LOW");
LogRegisterValue(FTS_REG_IC_VERSION, "IC_VERSION");
node_.CreateBool("needs_firmware_update", device_info.needs_firmware().value(), &values_);
node_.CreateUint("panel_type", static_cast<uint32_t>(panel_type), &values_);
fdf::info("Panel type: {}", static_cast<uint32_t>(panel_type));
// 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);
total_report_count_ = metrics_root_.CreateUint("total_report_count", 0);
last_event_timestamp_ = metrics_root_.CreateUint("last_event_timestamp", 0);
zx::result connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
fdf::error("Failed to bind devfs connector: {}", connector);
return connector.take_error();
}
fuchsia_driver_framework::DevfsAddArgs devfs({
.connector = std::move(connector.value()),
.class_name = "input-report",
.connector_supports = fuchsia_device_fs::ConnectionType::kController,
});
zx::result child = AddOwnedChild(kChildNodeName, devfs);
if (child.is_error()) {
fdf::error("Failed to create child: {}", child);
return child.take_error();
}
child_ = std::move(child.value());
return zx::ok();
}
void FtDevice::GetInputReportsReader(GetInputReportsReaderRequestView request,
GetInputReportsReaderCompleter::Sync& completer) {
const zx_status_t status = readers_.CreateReader(dispatcher(), std::move(request->reader));
if (status != ZX_OK) {
fdf::error("Failed to create reader: {}", zx_status_get_string(status));
}
}
void FtDevice::GetDescriptor(GetDescriptorCompleter::Sync& completer) {
fidl::Arena<kFeatureAndDescriptorBufferSize> allocator;
auto device_info = fuchsia_input_report::wire::DeviceInformation::Builder(allocator);
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> contacts(allocator,
kMaxPoints);
for (auto& c : contacts) {
c = fuchsia_input_report::wire::ContactInputDescriptor::Builder(allocator)
.position_x(XYAxis(x_max_))
.position_y(XYAxis(y_max_))
.Build();
}
const auto input = fuchsia_input_report::wire::TouchInputDescriptor::Builder(allocator)
.touch_type(fuchsia_input_report::wire::TouchType::kTouchscreen)
.max_contacts(kMaxPoints)
.contacts(contacts)
.Build();
const auto touch =
fuchsia_input_report::wire::TouchDescriptor::Builder(allocator).input(input).Build();
const auto descriptor = fuchsia_input_report::wire::DeviceDescriptor::Builder(allocator)
.device_information(device_info.Build())
.touch(touch)
.Build();
completer.Reply(descriptor);
}
// simple i2c read for reading one register location
// intended mostly for debug purposes
zx::result<uint8_t> FtDevice::Read(uint8_t addr) {
std::array<uint8_t, 1> rbuf;
zx::result result = i2c_.WriteReadSync(std::array<uint8_t, 1>{addr}, rbuf);
if (result.is_error()) {
fdf::error("Failed to write and read: {}", result);
return result.take_error();
}
return zx::ok(rbuf[0]);
}
zx::result<> FtDevice::Read(uint8_t addr, std::span<uint8_t> dst) {
// TODO(bradenkell): Remove this workaround when transfers of more than 8 bytes are supported on
// the MT8167.
size_t offset = 0;
while (offset < dst.size()) {
const size_t remaining = dst.size() - offset;
const size_t transfer_size = std::min(remaining, kMaxI2cTransferLength);
const std::array<uint8_t, 1> write_data = {static_cast<uint8_t>(addr + offset)};
zx::result result = i2c_.WriteReadSync(write_data, dst.subspan(offset, transfer_size));
if (result.is_error()) {
fdf::error("Failed to read i2c: {}", result);
return result.take_error();
}
offset += transfer_size;
}
return zx::ok();
}
void FtDevice::LogRegisterValue(uint8_t addr, std::string_view name) {
zx::result result = Read(addr);
if (result.is_ok()) {
uint8_t value = result.value();
node_.CreateByteVector(name, {&value, sizeof(value)}, &values_);
fdf::info(" {:16}: {:#02x}", name, value);
} else {
node_.CreateString(name, "error", &values_);
fdf::error(" {:16}: error {}", name, result);
}
}
void FtDevice::DevfsConnect(fidl::ServerEnd<fuchsia_input_report::InputDevice> server) {
bindings_.AddBinding(dispatcher(), std::move(server), this, fidl::kIgnoreBindingClosure);
}
} // namespace ft
FUCHSIA_DRIVER_EXPORT(ft::FtDevice);