blob: 103cd14a2adb3571767c9ad4c02d791dc447303d [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 "tcs3400.h"
#include <fidl/fuchsia.input.report/cpp/wire.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-protocol/i2c.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/fidl/llcpp/server.h>
#include <lib/zx/clock.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include <ddktl/fidl.h>
#include <ddktl/metadata/light-sensor.h>
#include <fbl/auto_lock.h>
#include "src/devices/light-sensor/drivers/ams-light/tcs3400_light_bind.h"
#include "tcs3400-regs.h"
namespace {
constexpr zx_duration_t INTERRUPTS_HYSTERESIS = ZX_MSEC(100);
constexpr uint8_t SAMPLES_TO_TRIGGER = 0x01;
// Repeat saturated log line every two minutes
constexpr uint16_t kSaturatedLogTimeSecs = 120;
// Bright, not saturated values to return when saturated
constexpr uint16_t kMaxSaturationRed = 21'067;
constexpr uint16_t kMaxSaturationGreen = 20'395;
constexpr uint16_t kMaxSaturationBlue = 20'939;
constexpr uint16_t kMaxSaturationClear = 65'085;
constexpr int64_t kIntegrationTimeStepSizeMicroseconds = 2780;
constexpr int64_t kMinIntegrationTimeStep = 1;
constexpr int64_t kMaxIntegrationTimeStep = 256;
#define GET_BYTE(val, shift) static_cast<uint8_t>(((val) >> (shift)) & 0xFF)
// clang-format off
// zx_port_packet::type
#define TCS_SHUTDOWN 0x01
#define TCS_CONFIGURE 0x02
#define TCS_INTERRUPT 0x03
#define TCS_REARM_IRQ 0x04
#define TCS_POLL 0x05
// clang-format on
constexpr fuchsia_input_report::wire::Axis kLightSensorAxis = {
.range = {.min = 0, .max = UINT16_MAX},
.unit =
{
.type = fuchsia_input_report::wire::UnitType::kOther,
.exponent = 0,
},
};
constexpr fuchsia_input_report::wire::Axis kReportIntervalAxis = {
.range = {.min = 0, .max = INT64_MAX},
.unit =
{
.type = fuchsia_input_report::wire::UnitType::kSeconds,
.exponent = -6,
},
};
constexpr fuchsia_input_report::wire::Axis kSensitivityAxis = {
.range = {.min = 1, .max = 64},
.unit =
{
.type = fuchsia_input_report::wire::UnitType::kOther,
.exponent = 0,
},
};
constexpr fuchsia_input_report::wire::Axis kSamplingRateAxis = {
.range = {.min = kIntegrationTimeStepSizeMicroseconds,
.max = kIntegrationTimeStepSizeMicroseconds * kMaxIntegrationTimeStep},
.unit =
{
.type = fuchsia_input_report::wire::UnitType::kSeconds,
.exponent = -6,
},
};
constexpr fuchsia_input_report::wire::SensorAxis MakeLightSensorAxis(
fuchsia_input_report::wire::SensorType type) {
return {.axis = kLightSensorAxis, .type = type};
}
template <typename T>
bool FeatureValueValid(int64_t value, const T& axis) {
return value >= axis.range.min && value <= axis.range.max;
}
} // namespace
namespace tcs {
void Tcs3400InputReport::ToFidlInputReport(
fidl::WireTableBuilder<::fuchsia_input_report::wire::InputReport>& input_report,
fidl::AnyArena& allocator) {
fidl::VectorView<int64_t> values(allocator, 4);
values[0] = illuminance;
values[1] = red;
values[2] = green;
values[3] = blue;
auto sensor_report =
fuchsia_input_report::wire::SensorInputReport::Builder(allocator).values(values);
input_report.event_time(event_time.get()).sensor(sensor_report.Build());
}
fuchsia_input_report::wire::FeatureReport Tcs3400FeatureReport::ToFidlFeatureReport(
fidl::AnyArena& allocator) const {
fidl::VectorView<int64_t> sens(allocator, 1);
sens[0] = sensitivity;
fidl::VectorView<int64_t> thresh_high(allocator, 1);
thresh_high[0] = threshold_high;
fidl::VectorView<int64_t> thresh_low(allocator, 1);
thresh_low[0] = threshold_low;
const auto sensor_report = fuchsia_input_report::wire::SensorFeatureReport::Builder(allocator)
.report_interval(report_interval_us)
.reporting_state(reporting_state)
.sensitivity(sens)
.threshold_high(thresh_high)
.threshold_low(thresh_low)
.sampling_rate(integration_time_us)
.Build();
return fuchsia_input_report::wire::FeatureReport::Builder(allocator)
.sensor(sensor_report)
.Build();
}
zx::status<Tcs3400InputReport> Tcs3400Device::ReadInputRpt() {
Tcs3400InputReport report{.event_time = zx::clock::get_monotonic()};
bool saturatedReading = false;
struct Regs {
int64_t* out;
uint8_t reg_h;
uint8_t reg_l;
} regs[] = {
{&report.illuminance, TCS_I2C_CDATAH, TCS_I2C_CDATAL},
{&report.red, TCS_I2C_RDATAH, TCS_I2C_RDATAL},
{&report.green, TCS_I2C_GDATAH, TCS_I2C_GDATAL},
{&report.blue, TCS_I2C_BDATAH, TCS_I2C_BDATAL},
};
for (const auto& i : regs) {
uint8_t buf_h, buf_l;
zx_status_t status;
// Read lower byte first, the device holds upper byte of a sample in a shadow register after
// a lower byte read
status = ReadReg(i.reg_l, buf_l);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c_write_read_sync failed: %d", status);
return zx::error(status);
}
status = ReadReg(i.reg_h, buf_h);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c_write_read_sync failed: %d", status);
return zx::error(status);
}
auto out = static_cast<uint16_t>(static_cast<float>(((buf_h & 0xFF) << 8) | (buf_l & 0xFF)));
// Use memcpy here because i.out is a misaligned pointer and dereferencing a
// misaligned pointer is UB. This ends up getting lowered to a 16-bit store.
memcpy(i.out, &out, sizeof(out));
saturatedReading |= (out == 65'535);
zxlogf(DEBUG, "raw: 0x%04X again: %u atime: %u", out, again_, atime_);
}
if (saturatedReading) {
// Saturated, ignoring the IR channel because we only looked at RGBC.
// Return very bright value so that consumers can adjust screens etc accordingly.
report.red = kMaxSaturationRed;
report.green = kMaxSaturationGreen;
report.blue = kMaxSaturationBlue;
report.illuminance = kMaxSaturationClear;
// log one message when saturation starts and then
if (!isSaturated_ || difftime(time(nullptr), lastSaturatedLog_) >= kSaturatedLogTimeSecs) {
zxlogf(INFO, "sensor is saturated");
time(&lastSaturatedLog_);
}
} else {
if (isSaturated_) {
zxlogf(INFO, "sensor is no longer saturated");
}
}
isSaturated_ = saturatedReading;
return zx::ok(report);
}
int Tcs3400Device::Thread() {
// Both polling and interrupts are supported simultaneously
zx_time_t poll_timeout = ZX_TIME_INFINITE;
zx_time_t irq_rearm_timeout = ZX_TIME_INFINITE;
for (;;) {
zx_port_packet_t packet;
zx_time_t timeout = std::min(poll_timeout, irq_rearm_timeout);
zx_status_t status = port_.wait(zx::time(timeout), &packet);
if (status != ZX_OK && status != ZX_ERR_TIMED_OUT) {
zxlogf(ERROR, "port wait failed: %d", status);
return thrd_error;
}
if (status == ZX_ERR_TIMED_OUT) {
if (timeout == irq_rearm_timeout) {
packet.key = TCS_REARM_IRQ;
} else {
packet.key = TCS_POLL;
}
}
Tcs3400FeatureReport feature_report;
{
fbl::AutoLock lock(&feature_lock_);
feature_report = feature_rpt_;
}
switch (packet.key) {
case TCS_SHUTDOWN:
zxlogf(INFO, "shutting down");
return thrd_success;
case TCS_CONFIGURE:
if (feature_report.report_interval_us == 0) { // per spec 0 is device's default
poll_timeout = ZX_TIME_INFINITE; // we define the default as no polling
} else {
poll_timeout = zx_deadline_after(ZX_USEC(feature_report.report_interval_us));
}
{
uint8_t control_reg = 0;
// clang-format off
if (feature_report.sensitivity == 4) control_reg = 1;
if (feature_report.sensitivity == 16) control_reg = 2;
if (feature_report.sensitivity == 64) control_reg = 3;
// clang-format on
again_ = static_cast<uint8_t>(feature_report.sensitivity);
const int64_t atime =
feature_report.integration_time_us / kIntegrationTimeStepSizeMicroseconds;
atime_ = static_cast<uint8_t>(kMaxIntegrationTimeStep - atime);
struct Setup {
uint8_t cmd;
uint8_t val;
} __PACKED setup[] = {
// First we don't set TCS_I2C_ENABLE_ADC_ENABLE to disable the sensor.
{TCS_I2C_ENABLE, TCS_I2C_ENABLE_POWER_ON | TCS_I2C_ENABLE_INT_ENABLE},
{TCS_I2C_AILTL, GET_BYTE(feature_report.threshold_low, 0)},
{TCS_I2C_AILTH, GET_BYTE(feature_report.threshold_low, 8)},
{TCS_I2C_AIHTL, GET_BYTE(feature_report.threshold_high, 0)},
{TCS_I2C_AIHTH, GET_BYTE(feature_report.threshold_high, 8)},
{TCS_I2C_PERS, SAMPLES_TO_TRIGGER},
{TCS_I2C_CONTROL, control_reg},
{TCS_I2C_ATIME, atime_},
// We now do set TCS_I2C_ENABLE_ADC_ENABLE to re-enable the sensor.
{TCS_I2C_ENABLE,
TCS_I2C_ENABLE_POWER_ON | TCS_I2C_ENABLE_ADC_ENABLE | TCS_I2C_ENABLE_INT_ENABLE},
};
for (const auto& i : setup) {
status = WriteReg(i.cmd, i.val);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c_write_sync failed: %d", status);
break; // do not exit thread, future transactions may succeed
}
}
}
break;
case TCS_INTERRUPT:
zx_interrupt_ack(irq_.get()); // rearm interrupt at the IRQ level
{
const zx::status<Tcs3400InputReport> report = ReadInputRpt();
if (report.is_error()) {
irq_rearm_timeout = zx_deadline_after(INTERRUPTS_HYSTERESIS);
break;
}
if (feature_report.reporting_state ==
fuchsia_input_report::wire::SensorReportingState::kReportNoEvents) {
irq_rearm_timeout = zx_deadline_after(INTERRUPTS_HYSTERESIS);
break;
}
if (report->illuminance > feature_report.threshold_high ||
report->illuminance < feature_report.threshold_low) {
readers_.SendReportToAllReaders(*report);
}
fbl::AutoLock lock(&input_lock_);
input_rpt_ = *report;
}
irq_rearm_timeout = zx_deadline_after(INTERRUPTS_HYSTERESIS);
break;
case TCS_REARM_IRQ:
// rearm interrupt at the device level
{
status = WriteReg(TCS_I2C_AICLEAR, 0x00);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c_write_sync failed: %d", status);
// Continue on error, future transactions may succeed
}
}
irq_rearm_timeout = ZX_TIME_INFINITE;
break;
case TCS_POLL:
if (feature_report.reporting_state !=
fuchsia_input_report::wire::SensorReportingState::kReportAllEvents) {
break;
}
{
const zx::status<Tcs3400InputReport> report = ReadInputRpt();
if (report.is_ok()) {
readers_.SendReportToAllReaders(*report);
fbl::AutoLock lock(&input_lock_);
input_rpt_ = *report;
}
}
poll_timeout += ZX_USEC(feature_report.report_interval_us);
zx_time_t now = zx_clock_get_monotonic();
if (now > poll_timeout) {
poll_timeout = zx_deadline_after(ZX_USEC(feature_report.report_interval_us));
}
break;
}
}
return thrd_success;
}
void Tcs3400Device::GetInputReportsReader(GetInputReportsReaderRequestView request,
GetInputReportsReaderCompleter::Sync& completer) {
readers_.CreateReader(loop_.dispatcher(), std::move(request->reader));
sync_completion_signal(&next_reader_wait_); // Only for tests.
}
void Tcs3400Device::GetDescriptor(GetDescriptorRequestView request,
GetDescriptorCompleter::Sync& completer) {
using SensorAxisVector = fidl::VectorView<fuchsia_input_report::wire::SensorAxis>;
fidl::Arena<kFeatureAndDescriptorBufferSize> allocator;
fuchsia_input_report::wire::DeviceInfo device_info;
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::kAmsLightSensor);
auto sensor_axes = SensorAxisVector(allocator, 4);
sensor_axes[0] = MakeLightSensorAxis(fuchsia_input_report::wire::SensorType::kLightIlluminance);
sensor_axes[1] = MakeLightSensorAxis(fuchsia_input_report::wire::SensorType::kLightRed);
sensor_axes[2] = MakeLightSensorAxis(fuchsia_input_report::wire::SensorType::kLightGreen);
sensor_axes[3] = MakeLightSensorAxis(fuchsia_input_report::wire::SensorType::kLightBlue);
fidl::VectorView<fuchsia_input_report::wire::SensorInputDescriptor> input_descriptor(allocator,
1);
input_descriptor[0] = fuchsia_input_report::wire::SensorInputDescriptor::Builder(allocator)
.values(sensor_axes)
.Build();
auto sensitivity_axes = SensorAxisVector(allocator, 1);
sensitivity_axes[0] = {
.axis = kSensitivityAxis,
.type = fuchsia_input_report::wire::SensorType::kLightIlluminance,
};
auto threshold_high_axes = SensorAxisVector(allocator, 1);
threshold_high_axes[0] =
MakeLightSensorAxis(fuchsia_input_report::wire::SensorType::kLightIlluminance);
auto threshold_low_axes = SensorAxisVector(allocator, 1);
threshold_low_axes[0] =
MakeLightSensorAxis(fuchsia_input_report::wire::SensorType::kLightIlluminance);
fidl::VectorView<fuchsia_input_report::wire::SensorFeatureDescriptor> feature_descriptor(
allocator, 1);
feature_descriptor[0] = fuchsia_input_report::wire::SensorFeatureDescriptor::Builder(allocator)
.report_interval(kReportIntervalAxis)
.supports_reporting_state(true)
.sensitivity(sensitivity_axes)
.threshold_high(threshold_high_axes)
.threshold_low(threshold_low_axes)
.sampling_rate(kSamplingRateAxis)
.Build();
const auto sensor_descriptor = fuchsia_input_report::wire::SensorDescriptor::Builder(allocator)
.input(input_descriptor)
.feature(feature_descriptor)
.Build();
const auto descriptor = fuchsia_input_report::wire::DeviceDescriptor::Builder(allocator)
.device_info(device_info)
.sensor(sensor_descriptor)
.Build();
completer.Reply(descriptor);
}
void Tcs3400Device::SendOutputReport(SendOutputReportRequestView request,
SendOutputReportCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void Tcs3400Device::GetFeatureReport(GetFeatureReportRequestView request,
GetFeatureReportCompleter::Sync& completer) {
fbl::AutoLock lock(&feature_lock_);
fidl::Arena<kFeatureAndDescriptorBufferSize> allocator;
completer.ReplySuccess(feature_rpt_.ToFidlFeatureReport(allocator));
}
void Tcs3400Device::SetFeatureReport(SetFeatureReportRequestView request,
SetFeatureReportCompleter::Sync& completer) {
const auto& report = request->report;
if (!report.has_sensor()) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (!report.sensor().has_report_interval() || report.sensor().report_interval() < 0) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (!report.sensor().has_sensitivity() || report.sensor().sensitivity().count() != 1 ||
!FeatureValueValid(report.sensor().sensitivity()[0], kSensitivityAxis)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
const int64_t gain = report.sensor().sensitivity()[0];
if (!(gain == 1 || gain == 4 || gain == 16 || gain == 64)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (!report.sensor().has_threshold_high() || report.sensor().threshold_high().count() != 1 ||
!FeatureValueValid(report.sensor().threshold_high()[0], kLightSensorAxis)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (!report.sensor().has_threshold_low() || report.sensor().threshold_low().count() != 1 ||
!FeatureValueValid(report.sensor().threshold_low()[0], kLightSensorAxis)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (!report.sensor().has_sampling_rate()) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
const int64_t atime = report.sensor().sampling_rate() / kIntegrationTimeStepSizeMicroseconds;
if (atime < 1 || atime > 256) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
{
fbl::AutoLock lock(&feature_lock_);
feature_rpt_.report_interval_us = report.sensor().report_interval();
feature_rpt_.reporting_state = report.sensor().reporting_state();
feature_rpt_.sensitivity = report.sensor().sensitivity()[0];
feature_rpt_.threshold_high = report.sensor().threshold_high()[0];
feature_rpt_.threshold_low = report.sensor().threshold_low()[0];
feature_rpt_.integration_time_us = atime * kIntegrationTimeStepSizeMicroseconds;
}
zx_port_packet packet = {TCS_CONFIGURE, ZX_PKT_TYPE_USER, ZX_OK, {}};
zx_status_t status = port_.queue(&packet);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
zxlogf(ERROR, "zx_port_queue failed: %d", status);
completer.ReplyError(status);
}
}
void Tcs3400Device::GetInputReport(GetInputReportRequestView request,
GetInputReportCompleter::Sync& completer) {
if (request->device_type != fuchsia_input_report::wire::DeviceType::kSensor) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
{
fbl::AutoLock lock(&feature_lock_);
if (feature_rpt_.reporting_state !=
fuchsia_input_report::wire::SensorReportingState::kReportAllEvents) {
// Light sensor data isn't continuously being read -- the data we have might be far out of
// date, and we can't block to read new data from the sensor.
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
}
fidl::Arena<kFeatureAndDescriptorBufferSize> allocator;
auto report = fuchsia_input_report::wire::InputReport::Builder(allocator);
{
fbl::AutoLock lock(&input_lock_);
if (!input_rpt_.is_valid()) {
// The driver is in the right mode, but hasn't had a chance to read from the sensor yet.
completer.ReplyError(ZX_ERR_SHOULD_WAIT);
return;
}
input_rpt_.ToFidlInputReport(report, allocator);
}
completer.ReplySuccess(report.Build());
}
void Tcs3400Device::WaitForNextReader() {
sync_completion_wait(&next_reader_wait_, ZX_TIME_INFINITE);
sync_completion_reset(&next_reader_wait_);
}
// static
zx::status<Tcs3400Device*> Tcs3400Device::CreateAndGetDevice(void* ctx, zx_device_t* parent) {
ddk::I2cChannel channel(parent, "i2c");
if (!channel.is_valid()) {
return zx::error(ZX_ERR_NO_RESOURCES);
}
ddk::GpioProtocolClient gpio(parent, "gpio");
if (!gpio.is_valid()) {
return zx::error(ZX_ERR_NO_RESOURCES);
}
zx::port port;
zx_status_t status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port);
if (status != ZX_OK) {
zxlogf(ERROR, "port_create failed: %d", status);
return zx::error(status);
}
auto dev =
std::make_unique<tcs::Tcs3400Device>(parent, std::move(channel), gpio, std::move(port));
status = dev->Bind();
if (status != ZX_OK) {
zxlogf(ERROR, "bind failed: %d", status);
return zx::error(status);
}
status = dev->DdkAdd("tcs-3400");
if (status != ZX_OK) {
zxlogf(ERROR, "DdkAdd failed: %d", status);
return zx::error(status);
}
// devmgr is now in charge of the memory for dev
return zx::ok(dev.release());
}
zx_status_t Tcs3400Device::Create(void* ctx, zx_device_t* parent) {
auto status = CreateAndGetDevice(ctx, parent);
return status.is_error() ? status.error_value() : ZX_OK;
}
zx_status_t Tcs3400Device::InitGain(uint8_t gain) {
if (!(gain == 1 || gain == 4 || gain == 16 || gain == 64)) {
zxlogf(WARNING, "Invalid gain (%u) using gain = 1", gain);
gain = 1;
}
again_ = gain;
zxlogf(DEBUG, "again (%u)", again_);
uint8_t reg;
// clang-format off
if (gain == 1) reg = 0;
if (gain == 4) reg = 1;
if (gain == 16) reg = 2;
if (gain == 64) reg = 3;
// clang-format on
auto status = WriteReg(TCS_I2C_CONTROL, reg);
if (status != ZX_OK) {
zxlogf(ERROR, "Setting gain failed %d", status);
return status;
}
return ZX_OK;
}
zx_status_t Tcs3400Device::InitMetadata() {
metadata::LightSensorParams parameters = {};
size_t actual = {};
auto status = device_get_metadata(parent(), DEVICE_METADATA_PRIVATE, &parameters,
sizeof(metadata::LightSensorParams), &actual);
if (status != ZX_OK || sizeof(metadata::LightSensorParams) != actual) {
zxlogf(ERROR, "Getting metadata failed %d", status);
return status;
}
// ATIME = 256 - Integration Time / 2.78 ms.
int64_t atime = parameters.integration_time_us / kIntegrationTimeStepSizeMicroseconds;
if (atime < kMinIntegrationTimeStep || atime > kMaxIntegrationTimeStep) {
atime = kMaxIntegrationTimeStep - 1;
zxlogf(WARNING, "Invalid integration time (%u) using atime = 1",
parameters.integration_time_us);
}
atime_ = static_cast<uint8_t>(kMaxIntegrationTimeStep - atime);
zxlogf(DEBUG, "atime (%u)", atime_);
{
status = WriteReg(TCS_I2C_ATIME, atime_);
if (status != ZX_OK) {
zxlogf(ERROR, "Setting integration time failed %d", status);
return status;
}
}
status = InitGain(parameters.gain);
if (status != ZX_OK) {
return status;
}
// Set the default features and send a configuration packet.
{
fbl::AutoLock lock(&feature_lock_);
// The device will trigger an interrupt outside the thresholds. These default threshold
// values effectively disable interrupts since we can't be outside this range, interrupts
// get effectively enabled when we configure a range that could trigger.
feature_rpt_.threshold_low = 0x0000;
feature_rpt_.threshold_high = 0xFFFF;
feature_rpt_.sensitivity = again_;
feature_rpt_.report_interval_us = parameters.polling_time_us;
feature_rpt_.reporting_state =
fuchsia_input_report::wire::SensorReportingState::kReportAllEvents;
feature_rpt_.integration_time_us = atime * kIntegrationTimeStepSizeMicroseconds;
}
zx_port_packet packet = {TCS_CONFIGURE, ZX_PKT_TYPE_USER, ZX_OK, {}};
status = port_.queue(&packet);
if (status != ZX_OK) {
zxlogf(ERROR, "zx_port_queue failed: %d", status);
return status;
}
return ZX_OK;
}
zx_status_t Tcs3400Device::ReadReg(uint8_t reg, uint8_t& output_value) {
uint8_t write_buffer[] = {reg};
constexpr uint8_t kNumberOfRetries = 2;
constexpr zx::duration kRetryDelay = zx::msec(1);
auto ret = i2c_.WriteReadSyncRetries(write_buffer, std::size(write_buffer), &output_value,
sizeof(uint8_t), kNumberOfRetries, kRetryDelay);
if (ret.status != ZX_OK) {
zxlogf(ERROR, "I2C write reg 0x%02X error %d, %d retries", reg, ret.status, ret.retries);
}
return ret.status;
}
zx_status_t Tcs3400Device::WriteReg(uint8_t reg, uint8_t value) {
uint8_t write_buffer[] = {reg, value};
constexpr uint8_t kNumberOfRetries = 2;
constexpr zx::duration kRetryDelay = zx::msec(1);
auto ret =
i2c_.WriteSyncRetries(write_buffer, std::size(write_buffer), kNumberOfRetries, kRetryDelay);
if (ret.status != ZX_OK) {
zxlogf(ERROR, "I2C write reg 0x%02X error %d, %d retries", reg, ret.status, ret.retries);
}
return ret.status;
}
zx_status_t Tcs3400Device::Bind() {
{
gpio_.ConfigIn(GPIO_NO_PULL);
auto status = gpio_.GetInterrupt(ZX_INTERRUPT_MODE_EDGE_LOW, &irq_);
if (status != ZX_OK) {
zxlogf(ERROR, "gpio_get_interrupt failed: %d", status);
return status;
}
}
zx_status_t status = irq_.bind(port_, TCS_INTERRUPT, 0);
if (status != ZX_OK) {
zxlogf(ERROR, "zx_interrupt_bind failed: %d", status);
return status;
}
status = InitMetadata();
if (status != ZX_OK) {
return status;
}
int rc = thrd_create_with_name(
&thread_, [](void* arg) -> int { return reinterpret_cast<Tcs3400Device*>(arg)->Thread(); },
reinterpret_cast<void*>(this), "tcs3400-thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
if ((status = loop_.StartThread("tcs3400-reader-thread")) != ZX_OK) {
zxlogf(ERROR, "failed to start loop: %d", status);
ShutDown();
return status;
}
return ZX_OK;
}
void Tcs3400Device::ShutDown() {
zx_port_packet packet = {TCS_SHUTDOWN, ZX_PKT_TYPE_USER, ZX_OK, {}};
zx_status_t status = port_.queue(&packet);
ZX_ASSERT(status == ZX_OK);
if (thread_) {
thrd_join(thread_, nullptr);
}
irq_.destroy();
loop_.Shutdown();
}
void Tcs3400Device::DdkUnbind(ddk::UnbindTxn txn) {
ShutDown();
txn.Reply();
}
void Tcs3400Device::DdkRelease() { delete this; }
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = Tcs3400Device::Create;
return ops;
}();
} // namespace tcs
ZIRCON_DRIVER(tcs3400_light, tcs::driver_ops, "zircon", "0.1");