blob: 2b90d376789dd715bba604d72908620a6a31b68a [file] [log] [blame]
// Copyright 2019 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 "cy8cmbr3108.h"
#include <unistd.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <hid/visalia-touch.h>
#include "src/ui/input/drivers/cypress/cypress_cy8cmbr3108-bind.h"
namespace cypress {
bool Cy8cmbr3108::RunTest(void* ctx, zx_device_t* parent, zx_handle_t channel) {
fbl::AllocChecker ac;
auto dev = std::unique_ptr<Cy8cmbr3108>(new (&ac) Cy8cmbr3108(parent));
if (!ac.check()) {
return false;
}
auto status = dev->Init();
if (status != ZX_OK) {
return false;
}
return dev->Test();
}
bool Cy8cmbr3108::Test() {
auto status_reg = SENSOR_EN::Get().FromValue(0);
zx_status_t status = RegisterOp(READ, status_reg);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to read sensor status :%d", status);
ShutDown();
return false;
}
zxlogf(INFO, "Sensors enabled : 0x%x", status_reg.reg_value());
zxlogf(INFO, "Touch the sensors to execute the test..");
auto button = BUTTON_STAT::Get().FromValue(0);
for (uint32_t i = 0; i < 100; i++) {
status = RegisterOp(READ, button);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to get button status :%d", status);
ShutDown();
return false;
}
zxlogf(INFO, "Button stat register - 0x%x", button.reg_value());
zx::nanosleep(zx::deadline_after(zx::duration(ZX_MSEC(200))));
}
zxlogf(INFO, "Cypress touch test passed");
ShutDown();
return true;
}
int Cy8cmbr3108::Thread() {
while (1) {
zx_port_packet_t packet;
zx_status_t status = port_.wait(zx::time::infinite(), &packet);
zxlogf(DEBUG, "%s msg received on port key %lu", __FUNCTION__, packet.key);
if (status != ZX_OK) {
zxlogf(ERROR, "%s port wait failed %d", __FUNCTION__, status);
return thrd_error;
}
if (packet.key == kPortKeyShutDown) {
zxlogf(INFO, "Cy8cmbr3108 thread shutting down");
return thrd_success;
}
visalia_touch_buttons_input_rpt_t input_rpt;
size_t out_len;
status = HidbusGetReport(0, BUTTONS_RPT_ID_INPUT, reinterpret_cast<uint8_t*>(&input_rpt),
sizeof(input_rpt), &out_len);
if (status != ZX_OK) {
zxlogf(ERROR, "%s HidbusGetReport failed %d", __FUNCTION__, status);
} else {
fbl::AutoLock lock(&client_lock_);
if (client_.is_valid()) {
client_.IoQueue(reinterpret_cast<uint8_t*>(&input_rpt),
sizeof(visalia_touch_buttons_input_rpt_t), zx_clock_get_monotonic());
// If report could not be filled, we do not ioqueue.
}
}
touch_irq_.ack();
} // while (1)
return thrd_success;
}
template <class DerivedType, class IntType, size_t AddrIntSize, class ByteOrder>
zx_status_t Cy8cmbr3108::RegisterOp(
uint32_t op, hwreg::I2cRegisterBase<DerivedType, IntType, AddrIntSize, ByteOrder>& reg) {
// cy8cmbr3108 is known to return NACK while it is busy processing commands or transitioning
// states. In this case, i2c commands need to be retried until a ACK is received. Typically it
// goes into deep-sleep state after 340ms of inactivity, so i2c command failures are quite common.
uint32_t timeout = 0;
zx_status_t status = ZX_ERR_INTERNAL;
while (status != ZX_OK) {
if (op == READ) {
status = reg.ReadFrom(i2c_);
} else if (op == WRITE) {
status = reg.WriteTo(i2c_);
}
if (status != ZX_OK) {
timeout++;
// i2c transaction is supposed to succeed at least by the end of 3 retries
if (timeout > 5) {
return status;
}
zx::nanosleep(zx::deadline_after(zx::duration(ZX_USEC(50))));
}
}
return status;
}
zx_status_t Cy8cmbr3108::HidbusStart(const hidbus_ifc_protocol_t* ifc) {
fbl::AutoLock lock(&client_lock_);
if (client_.is_valid()) {
return ZX_ERR_ALREADY_BOUND;
} else {
client_ = ddk::HidbusIfcProtocolClient(ifc);
}
return ZX_OK;
}
zx_status_t Cy8cmbr3108::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 Cy8cmbr3108::HidbusStop() {
fbl::AutoLock lock(&client_lock_);
client_.clear();
}
zx_status_t Cy8cmbr3108::HidbusGetDescriptor(hid_description_type_t desc_type,
uint8_t* out_data_buffer, size_t data_size,
size_t* out_data_actual) {
const uint8_t* desc;
size_t desc_size = get_visalia_touch_buttons_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 Cy8cmbr3108::HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, uint8_t* data,
size_t len, size_t* out_len) {
if (!data || !out_len) {
return ZX_ERR_INVALID_ARGS;
}
if (rpt_id != BUTTONS_RPT_ID_INPUT) {
return ZX_ERR_NOT_SUPPORTED;
}
*out_len = sizeof(visalia_touch_buttons_input_rpt_t);
if (*out_len > len) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
visalia_touch_buttons_input_rpt_t input_rpt = {};
input_rpt.rpt_id = BUTTONS_RPT_ID_INPUT;
auto button_reg = BUTTON_STAT::Get().FromValue(0);
auto status = RegisterOp(READ, button_reg);
if (status != ZX_OK) {
zxlogf(ERROR, "%s Failed to read button register %d", __FUNCTION__, status);
return status;
}
for (size_t i = 0; i < buttons_.size(); ++i) {
bool new_value = false; // A value true means a button is touched.
uint32_t mask = (1 << buttons_[i].idx);
if (mask & button_reg.reg_value()) {
new_value = true;
}
zxlogf(DEBUG, "%s new value %u for button %lu", __FUNCTION__, new_value, i);
fill_visalia_touch_buttons_report(buttons_[i].id, new_value, &input_rpt);
}
auto out = reinterpret_cast<visalia_touch_buttons_input_rpt_t*>(data);
*out = input_rpt;
return ZX_OK;
}
zx_status_t Cy8cmbr3108::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const uint8_t* data,
size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Cy8cmbr3108::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Cy8cmbr3108::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Cy8cmbr3108::HidbusGetProtocol(uint8_t* protocol) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t Cy8cmbr3108::HidbusSetProtocol(uint8_t protocol) { return ZX_OK; }
void Cy8cmbr3108::ShutDown() {
zx_port_packet packet = {kPortKeyShutDown, ZX_PKT_TYPE_USER, ZX_OK, {}};
zx_status_t status = port_.queue(&packet);
ZX_ASSERT(status == ZX_OK);
thrd_join(thread_, NULL);
touch_gpio_.ReleaseInterrupt();
touch_irq_.destroy();
fbl::AutoLock lock(&client_lock_);
client_.clear();
}
void Cy8cmbr3108::DdkUnbind(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
void Cy8cmbr3108::DdkRelease() { delete this; }
zx_status_t Cy8cmbr3108::InitializeProtocols() {
// Get I2C and GPIO protocol.
i2c_ = ddk::I2cProtocolClient(parent(), "i2c");
if (!i2c_.is_valid()) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_I2C not found", __func__);
return ZX_ERR_NO_RESOURCES;
}
touch_gpio_ = ddk::GpioProtocolClient(parent(), "gpio");
if (!touch_gpio_.is_valid()) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_GPIO not found", __func__);
return ZX_ERR_NO_RESOURCES;
}
// Get buttons metadata.
size_t actual = 0;
zx_status_t status = device_get_metadata_size(parent(), DEVICE_METADATA_PRIVATE, &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "%s device_get_metadata_size failed %d", __FILE__, status);
return ZX_OK;
}
size_t n_buttons = actual / sizeof(touch_button_config_t);
fbl::AllocChecker ac;
buttons_ = fbl::Array(new (&ac) touch_button_config_t[n_buttons], n_buttons);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
actual = 0;
status = device_get_metadata(parent(), DEVICE_METADATA_PRIVATE, buttons_.get(),
buttons_.size() * sizeof(touch_button_config_t), &actual);
if (status != ZX_OK || actual != buttons_.size() * sizeof(touch_button_config_t)) {
zxlogf(ERROR, "%s device_get_metadata failed %d", __FILE__, status);
return status;
}
return ZX_OK;
}
zx_status_t Cy8cmbr3108::Init() {
// wait upto I2CBOOT time
zx::nanosleep(zx::deadline_after(zx::duration(ZX_MSEC(15))));
zx_status_t status = InitializeProtocols();
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to initialize protocols %d", __FUNCTION__, status);
return status;
}
/* Note: The default sensor configuration works for visalia and hence not configuring those
* registers. Add those changes here if needed.*/
status = touch_gpio_.SetAltFunction(0);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to SetAltFunction touch GPIO %d", __FUNCTION__, status);
return status;
}
status = touch_gpio_.ConfigIn(GPIO_NO_PULL);
if (status != ZX_OK) {
zxlogf(ERROR, "%s:failed to ConfigIn touch GPIO %d", __func__, status);
return status;
}
status = touch_gpio_.GetInterrupt(ZX_INTERRUPT_MODE_EDGE_HIGH, &touch_irq_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to GetInterrupt touch GPIO %d", __func__, status);
return status;
}
status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s port_create failed %d", __FUNCTION__, status);
return status;
}
status = touch_irq_.bind(port_, kPortKeyTouchIrq, 0);
if (status != ZX_OK) {
zxlogf(ERROR, "%s zx_interrupt_bind failed %d", __FUNCTION__, status);
return status;
}
auto f = [](void* arg) -> int { return reinterpret_cast<Cy8cmbr3108*>(arg)->Thread(); };
int rc = thrd_create_with_name(&thread_, f, this, "cypress-irq-thread");
if (rc != thrd_success) {
ShutDown();
return ZX_ERR_INTERNAL;
}
return status;
}
zx_status_t Cy8cmbr3108::Bind() {
zx_status_t status = DdkAdd("cy8cmbr3108");
if (status != ZX_OK) {
zxlogf(ERROR, "%s DdkAdd failed: %d", __FUNCTION__, status);
}
return status;
}
zx_status_t Cy8cmbr3108::Create(void* ctx, zx_device_t* parent) {
auto dev = std::make_unique<Cy8cmbr3108>(parent);
zx_status_t status = ZX_OK;
if ((status = dev->Init()) != ZX_OK) {
return status;
}
if ((status = dev->Bind()) != ZX_OK) {
dev->ShutDown();
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = dev.release();
return ZX_OK;
}
static constexpr zx_driver_ops_t cypress_touch_driver_ops = []() {
zx_driver_ops_t driver_ops = {};
driver_ops.version = DRIVER_OPS_VERSION;
driver_ops.bind = Cy8cmbr3108::Create;
driver_ops.run_unit_tests = Cy8cmbr3108::RunTest;
return driver_ops;
}();
} // namespace cypress
// clang-format off
ZIRCON_DRIVER(cypress_cy8cmbr3108, cypress::cypress_touch_driver_ops, "zircon", "0.1");
//clang-format on