blob: a2ba858ca010804d8ad35cab4fe44f1eb9a61fe7 [file] [log] [blame]
// Copyright 2020 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 "shtv3.h"
#include <endian.h>
#include <lib/zx/time.h>
#include <ddk/debug.h>
#include <ddk/platform-defs.h>
#include <ddktl/fidl.h>
#include "src/devices/temperature/drivers/shtv3/shtv3-bind.h"
namespace {
constexpr uint16_t kSoftResetCommand = 0x805d;
// The maximum reset time is 240 us.
constexpr zx::duration kResetTime = zx::usec(500);
// Clock stretching disabled, read temperature first, normal mode.
constexpr uint16_t kStartMeasurementCommand = 0x7866;
// The maximum normal mode measurement time is 12.1 ms.
constexpr int kMeasurementRetries = 15;
constexpr zx::duration kMeasurementRetryInterval = zx::msec(1);
} // namespace
namespace temperature {
zx_status_t Shtv3Device::Create(void* ctx, zx_device_t* parent) {
ddk::I2cChannel i2c(parent);
if (!i2c.is_valid()) {
zxlogf(ERROR, "Failed to get I2C protocol");
return ZX_ERR_NO_RESOURCES;
}
auto dev = std::make_unique<Shtv3Device>(parent, i2c);
zx_status_t status = dev->Init();
if (status != ZX_OK) {
return status;
}
if ((status = dev->DdkAdd("shtv3")) != ZX_OK) {
zxlogf(ERROR, "DdkAdd failed: %d", status);
return status;
}
__UNUSED auto* _ = dev.release();
return ZX_OK;
}
zx_status_t Shtv3Device::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
temperature_fidl::Device::Dispatch(this, msg, &transaction);
return transaction.Status();
}
void Shtv3Device::DdkRelease() { delete this; }
void Shtv3Device::GetTemperatureCelsius(GetTemperatureCelsiusCompleter::Sync& completer) {
const zx::status<float> status = ReadTemperature();
completer.Reply(status.is_error() ? status.error_value() : ZX_OK, status.value_or(0.0f));
}
zx_status_t Shtv3Device::Init() {
zx_status_t status = Write16(kSoftResetCommand);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to send reset command: %d", status);
return status;
}
zx::nanosleep(zx::deadline_after(kResetTime));
return ZX_OK;
}
zx::status<float> Shtv3Device::ReadTemperature() {
zx_status_t status = Write16(kStartMeasurementCommand);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to send measurement command: %d", status);
return zx::error(status);
}
// Only read the temperature measurement, skip the CRC and humidity bytes.
zx::status<uint16_t> temp_data = zx::ok(0);
for (int i = 0; i < kMeasurementRetries; i++) {
if ((temp_data = Read16()).is_ok()) {
break;
}
zx::nanosleep(zx::deadline_after(kMeasurementRetryInterval));
}
if (temp_data.is_error()) {
zxlogf(ERROR, "Timed out waiting for temperature measurement: %d", temp_data.error_value());
return zx::error(temp_data.error_value());
}
const float temp_value = static_cast<float>(temp_data.value()) * 175;
return zx::ok((temp_value / 65536) - 45);
}
zx::status<uint16_t> Shtv3Device::Read16() {
uint16_t value;
zx_status_t status =
i2c_.WriteReadSync(nullptr, 0, reinterpret_cast<uint8_t*>(&value), sizeof(value));
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(be16toh(value));
}
zx_status_t Shtv3Device::Write16(uint16_t value) {
value = htobe16(value);
return i2c_.WriteSync(reinterpret_cast<uint8_t*>(&value), sizeof(value));
}
static constexpr zx_driver_ops_t shtv3_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = Shtv3Device::Create;
return ops;
}();
} // namespace temperature
ZIRCON_DRIVER(shtv3, temperature::shtv3_driver_ops, "zircon", "0.1");