| // 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"); |