blob: f217cc4fc3cc36378baf26c0f5ee8d5a91067f0e [file] [log] [blame]
// 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/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/hw/arch_ops.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/ddk/trace/event.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 <iterator>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.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) {
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(const uint8_t* buf) {
FtInputReport report;
const uint8_t contact_count = std::min(buf[0], static_cast<uint8_t>(report.contacts.max_size()));
buf += 1;
report.contact_count = 0;
for (size_t i = 0; i < contact_count; i++, buf += kFingerRptSize) {
if (((buf[0] & kFtTouchEventTypeMask) >> kFtTouchEventTypeShift) != FtTouchEventType::CONTACT) {
continue;
}
report.contacts[i].x = static_cast<uint16_t>(((buf[0] & 0x0f) << 8) + buf[1]);
report.contacts[i].y = static_cast<uint16_t>(((buf[2] & 0x0f) << 8) + buf[3]);
report.contacts[i].finger_id = static_cast<uint8_t>(buf[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) {
zxlogf(ERROR, "focaltouch: Interrupt error %d", status);
}
TRACE_DURATION("input", "FtDevice Read");
uint8_t i2c_buf[kMaxPoints * kFingerRptSize + 1];
status = Read(FTS_REG_CURPOINT, i2c_buf, kMaxPoints * kFingerRptSize + 1);
if (status == ZX_OK) {
auto timestamp = zx::time(interrupt->timestamp);
auto report = ParseReport(i2c_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 (i2c_buf[0] > 0) {
total_report_count_.Add(1);
last_event_timestamp_.Set(timestamp.get());
}
} else {
zxlogf(ERROR, "focaltouch: i2c read error");
}
irq_.ack();
}
zx_status_t FtDevice::Init() {
i2c_ = ddk::I2cChannel(parent(), "i2c");
if (!i2c_.is_valid()) {
zxlogf(ERROR, "failed to acquire i2c");
return ZX_ERR_NO_RESOURCES;
}
const char* kIntGpioFragmentName = "gpio-int";
zx::result int_gpio_client =
DdkConnectFragmentFidlProtocol<fuchsia_hardware_gpio::Service::Device>(parent(),
kIntGpioFragmentName);
if (int_gpio_client.is_error()) {
zxlogf(ERROR, "Failed to get gpio protocol from fragment %s: %s", kIntGpioFragmentName,
int_gpio_client.status_string());
return ZX_ERR_NO_RESOURCES;
}
int_gpio_.Bind(std::move(int_gpio_client.value()));
const char* kResetGpioFragmentName = "gpio-reset";
auto reset_gpio_client = DdkConnectFragmentFidlProtocol<fuchsia_hardware_gpio::Service::Device>(
parent(), kResetGpioFragmentName);
if (reset_gpio_client.is_error()) {
zxlogf(ERROR, "Failed to get gpio protocol from fragment %s: %s", kResetGpioFragmentName,
reset_gpio_client.status_string());
return ZX_ERR_NO_RESOURCES;
}
reset_gpio_.Bind(std::move(reset_gpio_client.value()));
{
fidl::WireResult result = int_gpio_->ConfigIn(fuchsia_hardware_gpio::GpioFlags::kNoPull);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send ConfigIn request to int gpio: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Failed to configure int gpio to input: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
fidl::WireResult interrupt = int_gpio_->GetInterrupt(ZX_INTERRUPT_MODE_EDGE_LOW);
if (!interrupt.ok()) {
zxlogf(ERROR, "Failed to send GetInterrupt request to int gpio: %s", interrupt.status_string());
return interrupt.status();
}
if (interrupt->is_error()) {
zxlogf(ERROR, "Failed to get interrupt from int gpio: %s",
zx_status_get_string(interrupt->error_value()));
return interrupt->error_value();
}
irq_ = std::move(interrupt.value()->irq);
irq_handler_.set_object(irq_.get());
irq_handler_.Begin(dispatcher_);
size_t actual;
FocaltechMetadata device_info;
zx_status_t status = device_get_metadata(parent(), DEVICE_METADATA_PRIVATE, &device_info,
sizeof(device_info), &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to get metadata: %s", zx_status_get_string(status));
return status;
}
if (sizeof(device_info) != actual) {
zxlogf(ERROR, "Incorrect metadata size: Expected %lu bytes but actual is %lu bytes",
sizeof(device_info), actual);
return ZX_ERR_INTERNAL;
}
if (device_info.device_id == FOCALTECH_DEVICE_FT3X27) {
x_max_ = kFt3x27XMax;
y_max_ = kFt3x27YMax;
} else if (device_info.device_id == FOCALTECH_DEVICE_FT6336) {
x_max_ = kFt6336XMax;
y_max_ = kFt6336YMax;
} else if (device_info.device_id == FOCALTECH_DEVICE_FT5726) {
x_max_ = kFt5726XMax;
y_max_ = kFt5726YMax;
} else if (device_info.device_id == FOCALTECH_DEVICE_FT5336) {
// 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;
} else {
zxlogf(ERROR, "focaltouch: unknown device ID %u", device_info.device_id);
return 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_->ConfigOut(0);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send ConfigOut request to reset gpio: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Failed to configure reset gpio to output: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
zx::nanosleep(zx::deadline_after(zx::msec(5)));
{
fidl::WireResult result = reset_gpio_->Write(1);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send Write request to reset gpio: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Failed to write to reset gpio: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
zx::nanosleep(zx::deadline_after(zx::msec(200)));
status = UpdateFirmwareIfNeeded(device_info);
if (status != ZX_OK) {
return 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");
if (device_info.needs_firmware) {
node_.CreateUint("Display_vendor", device_info.display_vendor, &values_);
node_.CreateUint("DDIC_version", device_info.ddic_version, &values_);
zxlogf(INFO, "Display vendor: %u", device_info.display_vendor);
zxlogf(INFO, "DDIC version: %u", device_info.ddic_version);
} else {
node_.CreateString("Display_vendor", "none", &values_);
node_.CreateString("DDIC_version", "none", &values_);
zxlogf(INFO, "Display vendor: none");
zxlogf(INFO, "DDIC version: none");
}
// 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);
return ZX_OK;
}
zx_status_t FtDevice::Create(void* ctx, zx_device_t* device) {
zxlogf(INFO, "focaltouch: driver started...");
auto ft_dev = std::make_unique<FtDevice>(
device, fdf_dispatcher_get_async_dispatcher(fdf_dispatcher_get_current_dispatcher()));
zx_status_t status = ft_dev->Init();
if (status != ZX_OK) {
zxlogf(ERROR, "focaltouch: Driver bind failed %d", status);
return status;
}
auto cleanup = fit::defer([&]() { ft_dev->ShutDown(); });
// Set scheduler role for device thread.
{
const char* role_name = "fuchsia.ui.input.drivers.focaltech.device";
status = device_set_profile_by_role(ft_dev->parent(), zx_thread_self(), role_name,
strlen(role_name));
if (status != ZX_OK) {
zxlogf(WARNING, "focaltouch: Failed to apply scheduler role: %s",
zx_status_get_string(status));
}
}
status = ft_dev->DdkAdd(ddk::DeviceAddArgs("focaltouch-HidDevice")
.set_inspect_vmo(ft_dev->inspector_.DuplicateVmo()));
if (status != ZX_OK) {
zxlogf(ERROR, "focaltouch: Could not create hid device: %d", status);
return status;
} else {
zxlogf(INFO, "focaltouch: Added hid device");
}
cleanup.cancel();
// device intentionally leaked as it is now held by DevMgr
[[maybe_unused]] auto ptr = ft_dev.release();
return ZX_OK;
}
void FtDevice::DdkRelease() { delete this; }
void FtDevice::DdkUnbind(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
zx_status_t FtDevice::ShutDown() {
irq_handler_.Cancel();
irq_.destroy();
return ZX_OK;
}
void FtDevice::GetInputReportsReader(GetInputReportsReaderRequestView request,
GetInputReportsReaderCompleter::Sync& completer) {
auto status = readers_.CreateReader(dispatcher_, std::move(request->reader));
if (status == ZX_OK) {
#ifdef FT_TEST
sync_completion_signal(&next_reader_wait_);
#endif
}
}
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
uint8_t FtDevice::Read(uint8_t addr) {
uint8_t rbuf;
i2c_.WriteReadSync(&addr, 1, &rbuf, 1);
return rbuf;
}
zx_status_t FtDevice::Read(uint8_t addr, uint8_t* buf, size_t len) {
// TODO(bradenkell): Remove this workaround when transfers of more than 8 bytes are supported on
// the MT8167.
while (len > 0) {
size_t readlen = std::min(len, kMaxI2cTransferLength);
zx_status_t status = i2c_.WriteReadSync(&addr, 1, buf, readlen);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to read i2c - %d", status);
return status;
}
addr = static_cast<uint8_t>(addr + readlen);
buf += readlen;
len -= readlen;
}
return ZX_OK;
}
void FtDevice::LogRegisterValue(uint8_t addr, const char* name) {
uint8_t value;
zx_status_t status = Read(addr, &value, sizeof(value));
if (status == ZX_OK) {
node_.CreateByteVector(name, {&value, sizeof(value)}, &values_);
zxlogf(INFO, " %-16s: 0x%02x", name, value);
} else {
node_.CreateString(name, "error", &values_);
zxlogf(ERROR, " %-16s: error %d", name, status);
}
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = FtDevice::Create;
return ops;
}();
} // namespace ft
ZIRCON_DRIVER(focaltech_touch, ft::driver_ops, "focaltech-touch", "0.1");