blob: 0b66909ea1ac88074c173b72aa985102bdd8cc9e [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 "gt92xx.h"
#include <lib/zx/profile.h>
#include <lib/zx/thread.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <iterator>
#include <utility>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/platform-defs.h>
#include <ddk/trace/event.h>
#include <ddktl/protocol/composite.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/vector.h>
namespace goodix {
// clang-format off
// Configuration data
// first two bytes contain starting register address (part of i2c transaction)
fbl::Vector<uint8_t> Gt92xxDevice::GetConfData() {
return {GT_REG_CONFIG_DATA >> 8,
GT_REG_CONFIG_DATA & 0xff,
0x5f, 0x00, 0x04, 0x58, 0x02, 0x05, 0xbd, 0xc0,
0x00, 0x08, 0x1e, 0x05, 0x50, 0x32, 0x00, 0x0b,
0x00, 0x00, 0x00, 0x00, 0x40, 0x12, 0x00, 0x17,
0x17, 0x19, 0x12, 0x8d, 0x2d, 0x0f, 0x3f, 0x41,
0xb2, 0x04, 0x00, 0x00, 0x00, 0xbc, 0x03, 0x1d,
0x1e, 0x80, 0x01, 0x00, 0x14, 0x46, 0x00, 0x00,
0x00, 0x00, 0x00, 0x37, 0x55, 0x8f, 0xc5, 0x02,
0x07, 0x11, 0x00, 0x04, 0x8a, 0x39, 0x00, 0x81,
0x3e, 0x00, 0x78, 0x44, 0x00, 0x71, 0x4a, 0x00,
0x6a, 0x51, 0x00, 0x6a, 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, 0x02, 0x00, 0x00, 0x00,
0x1c, 0x1a, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0e,
0x0c, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x00, 0x00,
0xff, 0xff, 0x1f, 0xe7, 0xff, 0xff, 0xff, 0x0f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x2a, 0x29,
0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21,
0x20, 0x1f, 0x1e, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 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, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x6e, 0x01 };
}
// clang-format on
int Gt92xxDevice::Thread() {
zx_status_t status;
zx::time timestamp;
zxlogf(INFO, "gt92xx: entering irq thread");
while (true) {
status = irq_.wait(&timestamp);
if (!running_.load()) {
return ZX_OK;
}
if (status != ZX_OK) {
zxlogf(ERROR, "gt92xx: Interrupt error %d", status);
}
TRACE_DURATION("input", "Gt92xxDevice Read");
uint8_t touch_stat = 0;
uint8_t retry_cnt = 0;
// Datasheet implies that it is not guaranteed that report will be
// ready when interrupt is generated, so allow a couple retries to check
// touch status.
while (!(touch_stat & GT_REG_TOUCH_STATUS_READY) && (retry_cnt < 3)) {
touch_stat = Read(GT_REG_TOUCH_STATUS);
if (!(touch_stat & GT_REG_TOUCH_STATUS_READY)) {
retry_cnt++;
zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
}
}
if (touch_stat & GT_REG_TOUCH_STATUS_READY) {
uint8_t num_reports = touch_stat & 0x0f;
FingerReport reports[kMaxPoints];
// Read touch reports
zx_status_t status = Read(GT_REG_REPORTS, reinterpret_cast<uint8_t*>(&reports),
static_cast<uint8_t>(sizeof(FingerReport) * kMaxPoints));
// Clear touch status after reading reports
Write(GT_REG_TOUCH_STATUS, 0);
if (status == ZX_OK) {
fbl::AutoLock lock(&client_lock_);
gt_rpt_.rpt_id = GT92XX_RPT_ID_TOUCH;
gt_rpt_.contact_count = num_reports;
// We are reusing same HID report as ft3x77 to simplify astro integration
// so we need to copy from device format to HID structure format
for (uint32_t i = 0; i < kMaxPoints; i++) {
gt_rpt_.fingers[i].finger_id =
static_cast<uint8_t>((reports[i].id << 2) | ((i < num_reports) ? 1 : 0));
gt_rpt_.fingers[i].y = reports[i].x;
gt_rpt_.fingers[i].x = reports[i].y;
}
if (client_.is_valid()) {
client_.IoQueue(reinterpret_cast<uint8_t*>(&gt_rpt_), sizeof(gt92xx_touch_t),
timestamp.get());
}
}
} else {
zxlogf(ERROR, "gt92xx: Errant interrupt, no report ready - %x", touch_stat);
}
}
zxlogf(INFO, "gt92xx: exiting");
return 0;
}
zx_status_t Gt92xxDevice::Create(zx_device_t* device) {
zxlogf(INFO, "gt92xx: driver started...");
ddk::CompositeProtocolClient composite(device);
if (!composite.is_valid()) {
zxlogf(ERROR, "Could not get composite protocol");
return ZX_ERR_NO_RESOURCES;
}
ddk::I2cChannel i2c(composite, "i2c");
if (!i2c.is_valid()) {
zxlogf(ERROR, "failed to acquire i2c");
return ZX_ERR_NO_RESOURCES;
}
ddk::GpioProtocolClient int_gpio(composite, "gpio-int");
if (!int_gpio.is_valid()) {
zxlogf(ERROR, "failed to acquire interrupt gpio");
return ZX_ERR_NO_RESOURCES;
}
ddk::GpioProtocolClient reset_gpio(composite, "gpio-reset");
if (!reset_gpio.is_valid()) {
zxlogf(ERROR, "failed to acquire reset gpio");
return ZX_ERR_NO_RESOURCES;
}
auto goodix_dev = std::make_unique<Gt92xxDevice>(device, i2c, int_gpio, reset_gpio);
zx_status_t status = goodix_dev->Init();
if (status != ZX_OK) {
zxlogf(ERROR, "Could not initialize gt92xx hardware %d", status);
return status;
}
auto thunk = [](void* arg) -> int { return reinterpret_cast<Gt92xxDevice*>(arg)->Thread(); };
auto cleanup = fbl::MakeAutoCall([&]() { goodix_dev->ShutDown(); });
goodix_dev->running_.store(true);
int ret = thrd_create_with_name(&goodix_dev->thread_, thunk, goodix_dev.get(), "gt92xx-thread");
ZX_DEBUG_ASSERT(ret == thrd_success);
// Set profile for bus transaction 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(goodix_dev->zxdev(), capacity.get(), deadline.get(),
period.get(), "gt92xx-thread", profile.reset_and_get_address());
if (status != ZX_OK) {
zxlogf(WARNING, "Gt92xxDevice::Create: Failed to get deadline profile: %s",
zx_status_get_string(status));
} else {
status = zx_object_set_profile(thrd_get_zx_handle(goodix_dev->thread_), profile.get(), 0);
if (status != ZX_OK) {
zxlogf(WARNING,
"Gt92xxDevice::Create: Failed to apply deadline profile to dispatch thread: %s",
zx_status_get_string(status));
}
}
}
status = goodix_dev->DdkAdd("gt92xx HidDevice");
if (status != ZX_OK) {
zxlogf(ERROR, "gt92xx: Could not create hid device: %d", status);
return status;
} else {
zxlogf(INFO, "gt92xx: Added hid device");
}
cleanup.cancel();
// device intentionally leaked as it is now held by DevMgr
__UNUSED auto ptr = goodix_dev.release();
return ZX_OK;
}
zx_status_t Gt92xxDevice::Init() {
// Hardware reset
HWReset();
uint8_t fw = Read(GT_REG_FIRMWARE);
if (fw != GT_FIRMWARE_MAGIC) {
zxlogf(ERROR, "Invalid gt92xx firmware configuration!");
return ZX_ERR_BAD_STATE;
}
// Device requires 50ms delay after this check (per datasheet)
zx_nanosleep(zx_deadline_after(ZX_MSEC(50)));
// Get the config data
fbl::Vector<uint8_t> Conf(GetConfData());
// Configuration data should span specific set of registers
// last register has flag to latch in new configuration, second
// to last register holds checksum of register values.
// Note: first two bytes of conf_data hold the 16-bit register address where
// the write will start.
ZX_DEBUG_ASSERT((Conf.size() - sizeof(uint16_t)) ==
(GT_REG_CONFIG_REFRESH - GT_REG_CONFIG_DATA + 1));
// Write conf data to registers
zx_status_t status = i2c_.WriteReadSync(&Conf[0], Conf.size(), NULL, 0);
if (status != ZX_OK) {
return status;
}
// Device requires 10ms delay to refresh configuration
zx_nanosleep(zx_deadline_after(ZX_MSEC(10)));
// Clear touch state in case there were spurious touches registered
// during startup
Write(GT_REG_TOUCH_STATUS, 0);
// Note: Our configuration inverts polarity of interrupt
// (datasheet implies it is active high)
status = int_gpio_.GetInterrupt(ZX_INTERRUPT_MODE_EDGE_LOW, &irq_);
return status;
}
void Gt92xxDevice::HWReset() {
// Hardware reset will also set the address of the controller to either
// 0x14 0r 0x5d. See the datasheet for explanation of sequence.
reset_gpio_.ConfigOut(0); // Make reset pin an output and pull low
int_gpio_.ConfigOut(0); // Make interrupt pin an output and pull low
// Delay for 100us
zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
reset_gpio_.Write(1); // Release the reset
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
int_gpio_.ConfigIn(0); // Make interrupt pin an input again;
zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); // Wait for reset to complete
}
zx_status_t Gt92xxDevice::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;
return ZX_OK;
}
void Gt92xxDevice::DdkRelease() { delete this; }
void Gt92xxDevice::DdkUnbind(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
zx_status_t Gt92xxDevice::ShutDown() {
running_.store(false);
irq_.destroy();
thrd_join(thread_, NULL);
{
fbl::AutoLock lock(&client_lock_);
client_.clear();
}
return ZX_OK;
}
zx_status_t Gt92xxDevice::HidbusGetDescriptor(hid_description_type_t desc_type,
void* out_data_buffer, size_t data_size,
size_t* out_data_actual) {
const uint8_t* desc;
size_t desc_size = get_gt92xx_report_desc(&desc);
if (data_size < desc_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(out_data_buffer, desc, desc_size);
*out_data_actual = desc_size;
return ZX_OK;
}
zx_status_t Gt92xxDevice::HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len,
size_t* out_len) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Gt92xxDevice::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const void* data,
size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Gt92xxDevice::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Gt92xxDevice::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Gt92xxDevice::HidbusGetProtocol(uint8_t* protocol) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t Gt92xxDevice::HidbusSetProtocol(uint8_t protocol) { return ZX_OK; }
void Gt92xxDevice::HidbusStop() {
fbl::AutoLock lock(&client_lock_);
client_.clear();
}
zx_status_t Gt92xxDevice::HidbusStart(const hidbus_ifc_protocol_t* ifc) {
fbl::AutoLock lock(&client_lock_);
if (client_.is_valid()) {
zxlogf(ERROR, "gt92xx: Already bound!");
return ZX_ERR_ALREADY_BOUND;
} else {
client_ = ddk::HidbusIfcProtocolClient(ifc);
zxlogf(INFO, "gt92xx: started");
}
return ZX_OK;
}
uint8_t Gt92xxDevice::Read(uint16_t addr) {
uint8_t rbuf;
Read(addr, &rbuf, 1);
return rbuf;
}
zx_status_t Gt92xxDevice::Read(uint16_t addr, uint8_t* buf, uint8_t len) {
uint8_t tbuf[2];
tbuf[0] = static_cast<uint8_t>(addr >> 8);
tbuf[1] = static_cast<uint8_t>(addr & 0xff);
return i2c_.WriteReadSync(tbuf, 2, buf, len);
}
zx_status_t Gt92xxDevice::Write(uint16_t addr, uint8_t val) {
uint8_t tbuf[3];
tbuf[0] = static_cast<uint8_t>(addr >> 8);
tbuf[1] = static_cast<uint8_t>(addr & 0xff);
tbuf[2] = val;
return i2c_.WriteReadSync(tbuf, 3, NULL, 0);
}
} // namespace goodix
zx_status_t gt92xx_bind(void* ctx, zx_device_t* device) {
return goodix::Gt92xxDevice::Create(device);
}
static constexpr zx_driver_ops_t gt92xx_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = gt92xx_bind;
return ops;
}();
// clang-format off
ZIRCON_DRIVER_BEGIN(gt92xx, gt92xx_driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GOOGLE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_ASTRO),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_ASTRO_GOODIXTOUCH),
ZIRCON_DRIVER_END(gt92xx)
// clang-format on