| // 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(×tamp); |
| 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*>(>_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 |