blob: 45dfbbb8747dc26ae279c9a353d31694ff3961c7 [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 <fuchsia/input/report/llcpp/fidl.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/hw/arch_ops.h>
#include <lib/ddk/hw/reg.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/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>
#include <fbl/span.h>
#include "src/ui/input/drivers/focaltech/focaltech_touch_bind.h"
namespace ft {
FtDevice::FtDevice(zx_device_t* device) : ddk::Device<FtDevice, ddk::Unbindable>(device) {}
void FtDevice::ParseReport(ft3x27_finger_t* rpt, uint8_t* buf) {
rpt->x = static_cast<uint16_t>(((buf[0] & 0x0f) << 8) + buf[1]);
rpt->y = static_cast<uint16_t>(((buf[2] & 0x0f) << 8) + buf[3]);
rpt->finger_id = static_cast<uint8_t>(((buf[2] >> 2) & FT3X27_FINGER_ID_CONTACT_MASK) |
(((buf[0] & 0xC0) == 0x80) ? 1 : 0));
}
int FtDevice::Thread() {
zx_status_t status;
zx::time timestamp;
zxlogf(INFO, "focaltouch: entering irq thread");
while (true) {
status = irq_.wait(&timestamp);
if (!running_.load()) {
return ZX_OK;
}
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) {
fbl::AutoLock lock(&client_lock_);
ft_rpt_.rpt_id = FT3X27_RPT_ID_TOUCH;
ft_rpt_.contact_count = i2c_buf[0];
for (uint i = 0; i < kMaxPoints; i++) {
ParseReport(&ft_rpt_.fingers[i], &i2c_buf[i * kFingerRptSize + 1]);
}
if (client_.is_valid()) {
client_.IoQueue(reinterpret_cast<uint8_t*>(&ft_rpt_), sizeof(ft3x27_touch_t),
timestamp.get());
}
} else {
zxlogf(ERROR, "focaltouch: i2c read error");
}
}
zxlogf(INFO, "focaltouch: exiting");
}
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;
}
int_gpio_ = ddk::GpioProtocolClient(parent(), "gpio-int");
if (!int_gpio_.is_valid()) {
zxlogf(ERROR, "failed to acquire int gpio");
return ZX_ERR_NO_RESOURCES;
}
reset_gpio_ = ddk::GpioProtocolClient(parent(), "gpio-reset");
if (!reset_gpio_.is_valid()) {
zxlogf(ERROR, "focaltouch: failed to acquire gpio");
return ZX_ERR_NO_RESOURCES;
}
int_gpio_.ConfigIn(GPIO_NO_PULL);
zx_status_t status = int_gpio_.GetInterrupt(ZX_INTERRUPT_MODE_EDGE_LOW, &irq_);
if (status != ZX_OK) {
return status;
}
size_t actual;
FocaltechMetadata device_info;
status = device_get_metadata(parent(), DEVICE_METADATA_PRIVATE, &device_info, sizeof(device_info),
&actual);
if (status != ZX_OK || sizeof(device_info) != actual) {
zxlogf(ERROR, "focaltouch: failed to read metadata");
return status == ZX_OK ? ZX_ERR_INTERNAL : status;
}
if (device_info.device_id == FOCALTECH_DEVICE_FT3X27) {
descriptor_len_ = get_ft3x27_report_desc(&descriptor_);
} else if (device_info.device_id == FOCALTECH_DEVICE_FT6336) {
descriptor_len_ = get_ft6336_report_desc(&descriptor_);
} else if (device_info.device_id == FOCALTECH_DEVICE_FT5726) {
descriptor_len_ = get_ft5726_report_desc(&descriptor_);
} 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.
reset_gpio_.ConfigOut(0);
zx::nanosleep(zx::deadline_after(zx::msec(5)));
reset_gpio_.Write(1);
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");
}
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);
zx_status_t status = ft_dev->Init();
if (status != ZX_OK) {
zxlogf(ERROR, "focaltouch: Driver bind failed %d", status);
return status;
}
auto thunk = [](void* arg) -> int { return reinterpret_cast<FtDevice*>(arg)->Thread(); };
auto cleanup = fit::defer([&]() { ft_dev->ShutDown(); });
ft_dev->running_.store(true);
int ret = thrd_create_with_name(&ft_dev->thread_, thunk, reinterpret_cast<void*>(ft_dev.get()),
"focaltouch-thread");
ZX_DEBUG_ASSERT(ret == thrd_success);
// 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(ft_dev->zxdev(), capacity.get(), deadline.get(), period.get(),
"focaltouch-thread", profile.reset_and_get_address());
if (status != ZX_OK) {
zxlogf(WARNING, "focaltouch: Failed to get deadline profile: %s",
zx_status_get_string(status));
} else {
status = zx_object_set_profile(thrd_get_zx_handle(ft_dev->thread_), profile.get(), 0);
if (status != ZX_OK) {
zxlogf(WARNING, "focaltouch: Failed to apply deadline profile to device thread: %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
__UNUSED auto ptr = ft_dev.release();
return ZX_OK;
}
zx_status_t FtDevice::HidbusQuery(uint32_t options, hid_info_t* info) {
if (!info) {
return ZX_ERR_INVALID_ARGS;
}
info->dev_num = 0;
info->device_class = HID_DEVICE_CLASS_OTHER;
info->boot_device = false;
info->vendor_id = static_cast<uint32_t>(fuchsia_input_report::wire::VendorId::kGoogle);
info->product_id = static_cast<uint32_t>(
fuchsia_input_report::wire::VendorGoogleProductId::kFocaltechTouchscreen);
return ZX_OK;
}
void FtDevice::DdkRelease() { delete this; }
void FtDevice::DdkUnbind(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
zx_status_t FtDevice::ShutDown() {
running_.store(false);
irq_.destroy();
thrd_join(thread_, NULL);
{
fbl::AutoLock lock(&client_lock_);
// client_.clear();
}
return ZX_OK;
}
zx_status_t FtDevice::HidbusGetDescriptor(hid_description_type_t desc_type,
uint8_t* out_data_buffer, size_t data_size,
size_t* out_data_actual) {
if (data_size < descriptor_len_) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(out_data_buffer, descriptor_, descriptor_len_);
*out_data_actual = descriptor_len_;
return ZX_OK;
}
zx_status_t FtDevice::HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, uint8_t* data, size_t len,
size_t* out_len) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t FtDevice::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const uint8_t* data,
size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t FtDevice::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t FtDevice::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t FtDevice::HidbusGetProtocol(uint8_t* protocol) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t FtDevice::HidbusSetProtocol(uint8_t protocol) { return ZX_OK; }
void FtDevice::HidbusStop() {
fbl::AutoLock lock(&client_lock_);
client_.clear();
}
zx_status_t FtDevice::HidbusStart(const hidbus_ifc_protocol_t* ifc) {
fbl::AutoLock lock(&client_lock_);
if (client_.is_valid()) {
zxlogf(ERROR, "focaltouch: Already bound!");
return ZX_ERR_ALREADY_BOUND;
} else {
client_ = ddk::HidbusIfcProtocolClient(ifc);
zxlogf(INFO, "focaltouch: started");
}
return ZX_OK;
}
// 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}, &values_);
zxlogf(INFO, " %-16s: 0x%02x", name, value);
} else {
node_.CreateString(name, "error", &values_);
zxlogf(ERROR, " %-16s: error %d", name, status);
}
}
zx_status_t FtDevice::UpdateFirmwareIfNeeded(const FocaltechMetadata& metadata) {
if (!metadata.needs_firmware) {
return ZX_OK;
}
fbl::Span<const uint8_t> firmware;
const fbl::Span<const FirmwareEntry> entries(kFirmwareEntries, kNumFirmwareEntries);
for (const auto& entry : entries) {
if (entry.display_vendor == metadata.display_vendor &&
entry.ddic_version == metadata.ddic_version) {
firmware = fbl::Span(entry.firmware_data, entry.firmware_size);
break;
}
}
if (firmware.empty()) {
zxlogf(WARNING, "No firmware found for vendor %u DDIC %u", metadata.display_vendor,
metadata.ddic_version);
}
// TODO(fxbug.dev/75032): Implement the firmware update process.
return ZX_OK;
}
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");