blob: 5526845af0da2db4dd805119c4a5f89643f05917 [file] [log] [blame]
// Copyright 2023 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 "aml-trip-device.h"
#include <fidl/fuchsia.hardware.trippoint/cpp/common_types.h>
#include <fidl/fuchsia.hardware.trippoint/cpp/wire_types.h>
#include <lib/ddk/debug.h>
#include <lib/driver/logging/cpp/logger.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <algorithm>
#include <src/devices/temperature/drivers/aml-trip/util.h>
#include "aml-tsensor-regs.h"
namespace temperature {
void AmlTripDevice::Init() {
InitSensor();
irq_handler_.set_object(irq_.get());
irq_handler_.Begin(dispatcher_);
}
void AmlTripDevice::Shutdown() {
irq_handler_.Cancel();
irq_.destroy();
}
void AmlTripDevice::QueueTripResult(TemperatureCelsius measured, uint32_t index) {
fuchsia_hardware_trippoint::wire::TripPointResult result;
ZX_ASSERT_MSG(index < kNumTripPoints, "QueueTripResult index out of range, index = %u", index);
if (!configured_trip_points_[index]) {
FDF_LOG(WARNING, "Trip point cleared before IRQ delivered. index = %u", index);
return;
}
result.measured_temperature_celsius = measured;
result.index = index;
pending_trips_.push_back(result);
}
TemperatureCelsius AmlTripDevice::ReadTemperatureCelsius() {
unsigned int count = 0;
unsigned int value_all = 0;
constexpr unsigned int kAmlTsValueCount = 0x10;
constexpr unsigned int kTvalueLBound = 0x18a9;
constexpr unsigned int kTvalueUBound = 0x32a6;
TemperatureCelsius result = 0.0f;
for (unsigned int i = 0; i < kAmlTsValueCount; i++) {
auto ts_stat0 = thermal::TsStat0::Get().ReadFrom(&sensor_mmio_);
auto tvalue = ts_stat0.temperature();
if ((tvalue >= kTvalueLBound) && (tvalue <= kTvalueUBound)) {
count++;
value_all += tvalue;
}
}
if (count != 0) {
result = tsensor_util::CodeToTempCelsius(value_all / count, trim_info_);
}
return result;
}
void AmlTripDevice::GetTemperatureCelsius(GetTemperatureCelsiusCompleter::Sync& completer) {
TemperatureCelsius temperature = ReadTemperatureCelsius();
completer.Reply(ZX_OK, temperature);
}
void AmlTripDevice::GetSensorName(GetSensorNameCompleter::Sync& completer) {
completer.Reply(fidl::StringView::FromExternal(name_));
}
void AmlTripDevice::GetTripPointDescriptors(GetTripPointDescriptorsCompleter::Sync& completer) {
std::vector<fuchsia_hardware_trippoint::wire::TripPointDescriptor> result(kNumTripPoints);
for (uint32_t i = 0; i < kNumTripPoints; i++) {
result[i].index = i;
result[i].type = GetTripPointType(i);
if (!configured_trip_points_[i].has_value()) {
result[i].configuration =
fuchsia_hardware_trippoint::wire::TripPointValue::WithClearedTripPoint(
fuchsia_hardware_trippoint::wire::ClearedTripPoint());
continue;
}
switch (configured_trip_points_[i]->direction) {
case TripPointDirection::Rise:
result[i].configuration =
fuchsia_hardware_trippoint::wire::TripPointValue::WithOneshotTempAboveTripPoint(
fuchsia_hardware_trippoint::wire::OneshotTempAboveTripPoint{
.critical_temperature_celsius = configured_trip_points_[i]->temperature});
break;
case TripPointDirection::Fall:
result[i].configuration =
fuchsia_hardware_trippoint::wire::TripPointValue::WithOneshotTempBelowTripPoint(
fuchsia_hardware_trippoint::wire::OneshotTempBelowTripPoint{
.critical_temperature_celsius = configured_trip_points_[i]->temperature});
break;
default:
ZX_PANIC("Invalid configured trip point type");
}
}
completer.ReplySuccess(
::fidl::VectorView<::fuchsia_hardware_trippoint::wire::TripPointDescriptor>::FromExternal(
result));
}
void AmlTripDevice::SetTripPoints(SetTripPointsRequestView request,
SetTripPointsCompleter::Sync& completer) {
if (request->descriptors.empty()) {
// No work to do.
completer.ReplySuccess();
return;
}
for (const auto& desc : request->descriptors) {
if (desc.index >= kNumTripPoints) {
FDF_LOG(ERROR, "Trip Point index out of bounds, (max = %u, requested = %u)", kNumTripPoints,
desc.index);
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
if (desc.type != GetTripPointType(desc.index)) {
FDF_LOG(ERROR, "The provided index does not match the trip point type. index = %u",
desc.index);
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (desc.configuration.is_oneshot_temp_below_trip_point()) {
if (desc.type != fuchsia_hardware_trippoint::TripPointType::kOneshotTempBelow) {
FDF_LOG(ERROR, "The provided configuration does not match the trip point type. index = %u",
desc.index);
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (std::isinf(
desc.configuration.oneshot_temp_below_trip_point().critical_temperature_celsius)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
} else if (desc.configuration.is_oneshot_temp_above_trip_point()) {
if (desc.type != fuchsia_hardware_trippoint::TripPointType::kOneshotTempAbove) {
FDF_LOG(ERROR, "The provided configuration does not match the trip point type. index = %u",
desc.index);
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
if (std::isinf(
desc.configuration.oneshot_temp_above_trip_point().critical_temperature_celsius)) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
} else if (desc.configuration.is_cleared_trip_point()) {
// Any trip point is allowed to be cleard so we deliberately skip this.
} else {
FDF_LOG(ERROR, "The provided configuration is unknown by this hardware. index = %u",
desc.index);
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
}
for (const auto& desc : request->descriptors) {
TemperatureCelsius thresh_temp;
bool trend;
configured_trip_point_t cfg;
configured_trip_points_[desc.index].reset();
// Part of the contract of this interface is also to clear any pending trip
// points for the trip point that's being reconfigured.
const auto new_end =
std::remove_if(pending_trips_.begin(), pending_trips_.end(),
[desc](fuchsia_hardware_trippoint::wire::TripPointResult r) {
return r.index == desc.index;
});
pending_trips_.erase(new_end, pending_trips_.end());
if (desc.configuration.is_oneshot_temp_above_trip_point()) {
thresh_temp = desc.configuration.oneshot_temp_above_trip_point().critical_temperature_celsius;
cfg.direction = TripPointDirection::Rise;
trend = true;
} else if (desc.configuration.is_oneshot_temp_below_trip_point()) {
thresh_temp = desc.configuration.oneshot_temp_below_trip_point().critical_temperature_celsius;
cfg.direction = TripPointDirection::Fall;
trend = false;
} else if (desc.configuration.is_cleared_trip_point()) {
AckAndDisableIrq(desc.index);
continue;
} else {
ZX_PANIC("Invalid trip point type");
}
cfg.temperature = thresh_temp;
AckAndDisableIrq(desc.index);
const uint32_t temp_code = tsensor_util::TempCelsiusToCode(thresh_temp, trend, trim_info_);
ProgramTripPoint(temp_code, desc.index);
configured_trip_points_[desc.index] = cfg;
EnableIrq(desc.index);
}
// In case the client has cleared all trip points but still has a read pending
// we want to complete that read request.
CompletePendingRead();
completer.ReplySuccess();
}
void AmlTripDevice::ProgramTripPoint(uint32_t temp_code, uint32_t index) {
switch (index) {
case 7:
thermal::TsCfgReg7::Get()
.ReadFrom(&sensor_mmio_)
.set_fall_th1(temp_code)
.WriteTo(&sensor_mmio_);
return;
case 6:
thermal::TsCfgReg7::Get()
.ReadFrom(&sensor_mmio_)
.set_fall_th0(temp_code)
.WriteTo(&sensor_mmio_);
return;
case 5:
thermal::TsCfgReg6::Get()
.ReadFrom(&sensor_mmio_)
.set_fall_th1(temp_code)
.WriteTo(&sensor_mmio_);
return;
case 4:
thermal::TsCfgReg6::Get()
.ReadFrom(&sensor_mmio_)
.set_fall_th0(temp_code)
.WriteTo(&sensor_mmio_);
return;
case 3:
thermal::TsCfgReg5::Get()
.ReadFrom(&sensor_mmio_)
.set_rise_th1(temp_code)
.WriteTo(&sensor_mmio_);
return;
case 2:
thermal::TsCfgReg5::Get()
.ReadFrom(&sensor_mmio_)
.set_rise_th0(temp_code)
.WriteTo(&sensor_mmio_);
return;
case 1:
thermal::TsCfgReg4::Get()
.ReadFrom(&sensor_mmio_)
.set_rise_th1(temp_code)
.WriteTo(&sensor_mmio_);
return;
case 0:
thermal::TsCfgReg4::Get()
.ReadFrom(&sensor_mmio_)
.set_rise_th0(temp_code)
.WriteTo(&sensor_mmio_);
return;
}
ZX_PANIC("Unhandled trip index: index = %u", index);
}
void AmlTripDevice::SetRebootTemperatureCelsius(TemperatureCelsius critical_temp_celsius) {
uint32_t reboot_val = tsensor_util::TempCelsiusToCode(critical_temp_celsius, true, trim_info_);
auto reboot_config = thermal::TsCfgReg2::Get().ReadFrom(&sensor_mmio_);
reboot_config.set_hi_temp_enable(1)
.set_reset_en(1)
.set_high_temp_times(AML_TS_REBOOT_TIME)
.set_high_temp_threshold(reboot_val << 4)
.WriteTo(&sensor_mmio_);
}
void AmlTripDevice::WaitForAnyTripPoint(WaitForAnyTripPointCompleter::Sync& completer) {
// Somebody else is already waiting on a trip point.
if (pending_read_) {
FDF_LOG(ERROR, "WaitForTripPoint is already bound");
completer.ReplyError(ZX_ERR_ALREADY_BOUND);
return;
}
if (!AtLeastOneTripConfigured()) {
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
pending_read_ = completer.ToAsync();
CompletePendingRead();
}
void AmlTripDevice::AckAndDisableIrq(uint32_t index) {
ZX_ASSERT_MSG(index < kNumTripPoints, "Trip Point index out of bounds, index = %u", index);
auto sensor_ctl = thermal::TsCfgReg1::Get().ReadFrom(&sensor_mmio_);
auto reg_value = sensor_ctl.reg_value();
const uint32_t en_mask = (1 << (IRQ_RISE_ENABLE_SHIFT + index));
const uint32_t clr_mask = (1 << (IRQ_RISE_STAT_CLR_SHIFT + index));
reg_value &= ~(en_mask);
reg_value |= clr_mask;
sensor_ctl.set_reg_value(reg_value);
sensor_ctl.WriteTo(&sensor_mmio_);
reg_value &= ~(clr_mask);
sensor_ctl.set_reg_value(reg_value);
sensor_ctl.WriteTo(&sensor_mmio_);
}
void AmlTripDevice::CompletePendingRead() {
if (!pending_read_) {
return;
}
if (!AtLeastOneTripConfigured()) {
pending_read_->ReplyError(ZX_ERR_BAD_STATE);
fidl::Status st = pending_read_->result_of_reply();
if (!st.ok()) {
FDF_LOG(ERROR, "Failed to complete a pending WaitForTripPoint: %s",
st.FormatDescription().c_str());
}
pending_read_.reset();
return;
}
if (pending_trips_.empty()) {
return;
}
fuchsia_hardware_trippoint::wire::TripPointResult result = pending_trips_.front();
pending_trips_.pop_front();
// Trip points are one-shot so once a trip point is consumed it is no longer
// configured.
configured_trip_points_[result.index].reset();
pending_read_->ReplySuccess(result);
fidl::Status st = pending_read_->result_of_reply();
if (!st.ok()) {
FDF_LOG(ERROR, "Failed to complete a pending WaitForTripPoint: %s",
st.FormatDescription().c_str());
}
pending_read_.reset();
}
bool AmlTripDevice::AtLeastOneTripConfigured() const {
return std::any_of(
configured_trip_points_.cbegin(), configured_trip_points_.cend(),
[](const std::optional<configured_trip_point_t>& tp) { return tp.has_value(); });
}
void AmlTripDevice::InitSensor() {
// Clear all IRQ's status.
thermal::TsCfgReg1::Get()
.ReadFrom(&sensor_mmio_)
.set_fall_th3_irq_stat_clr(1)
.set_fall_th2_irq_stat_clr(1)
.set_fall_th1_irq_stat_clr(1)
.set_fall_th0_irq_stat_clr(1)
.set_rise_th3_irq_stat_clr(1)
.set_rise_th2_irq_stat_clr(1)
.set_rise_th1_irq_stat_clr(1)
.set_rise_th0_irq_stat_clr(1)
.WriteTo(&sensor_mmio_);
thermal::TsCfgReg1::Get()
.ReadFrom(&sensor_mmio_)
.set_fall_th3_irq_stat_clr(0)
.set_fall_th2_irq_stat_clr(0)
.set_fall_th1_irq_stat_clr(0)
.set_fall_th0_irq_stat_clr(0)
.set_rise_th3_irq_stat_clr(0)
.set_rise_th2_irq_stat_clr(0)
.set_rise_th1_irq_stat_clr(0)
.set_rise_th0_irq_stat_clr(0)
.WriteTo(&sensor_mmio_);
// Disable All IRQs
thermal::TsCfgReg1::Get()
.ReadFrom(&sensor_mmio_)
.set_fall_th3_irq_en(0)
.set_fall_th2_irq_en(0)
.set_fall_th1_irq_en(0)
.set_fall_th0_irq_en(0)
.set_rise_th3_irq_en(0)
.set_rise_th2_irq_en(0)
.set_rise_th1_irq_en(0)
.set_rise_th0_irq_en(0)
.set_enable_irq(1)
.WriteTo(&sensor_mmio_);
thermal::TsCfgReg1::Get()
.ReadFrom(&sensor_mmio_)
.set_filter_en(1)
.set_ts_ana_en_vcm(1)
.set_ts_ana_en_vbg(1)
.set_bipolar_bias_current_input(AML_TS_CH_SEL)
.set_ts_ena_en_iptat(1)
.set_ts_dem_en(1)
.WriteTo(&sensor_mmio_);
}
void AmlTripDevice::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq,
zx_status_t status, const zx_packet_interrupt_t* interrupt) {
if (status != ZX_OK) {
FDF_LOG(ERROR, "IRQ wait returned: %s", zx_status_get_string(status));
return;
}
TemperatureCelsius measured_temperature = ReadTemperatureCelsius();
auto irq_stat = thermal::TsStat1::Get().ReadFrom(&sensor_mmio_);
if (irq_stat.rise_th3_irq()) {
QueueTripResult(measured_temperature, 3);
AckAndDisableIrq(3);
}
if (irq_stat.rise_th2_irq()) {
QueueTripResult(measured_temperature, 2);
AckAndDisableIrq(2);
}
if (irq_stat.rise_th1_irq()) {
QueueTripResult(measured_temperature, 1);
AckAndDisableIrq(1);
}
if (irq_stat.rise_th0_irq()) {
QueueTripResult(measured_temperature, 0);
AckAndDisableIrq(0);
}
if (irq_stat.fall_th3_irq()) {
QueueTripResult(measured_temperature, 7);
AckAndDisableIrq(7);
}
if (irq_stat.fall_th2_irq()) {
QueueTripResult(measured_temperature, 6);
AckAndDisableIrq(6);
}
if (irq_stat.fall_th1_irq()) {
QueueTripResult(measured_temperature, 5);
AckAndDisableIrq(5);
}
if (irq_stat.fall_th0_irq()) {
QueueTripResult(measured_temperature, 4);
AckAndDisableIrq(4);
}
// Notify anybody waiting for a trip point that there may be data available.
CompletePendingRead();
irq_.ack();
}
void AmlTripDevice::EnableIrq(uint32_t index) {
auto sensor_ctl = thermal::TsCfgReg1::Get().ReadFrom(&sensor_mmio_);
auto reg_value = sensor_ctl.reg_value();
reg_value |= (1 << (IRQ_RISE_ENABLE_SHIFT + index));
reg_value &= ~(1 << (IRQ_RISE_STAT_CLR_SHIFT + index));
sensor_ctl.set_reg_value(reg_value);
sensor_ctl.WriteTo(&sensor_mmio_);
}
fuchsia_hardware_trippoint::TripPointType AmlTripDevice::GetTripPointType(uint32_t index) {
ZX_ASSERT(index < kNumTripPoints);
if (index >= kFallTripRange.first && index <= kFallTripRange.second) {
return fuchsia_hardware_trippoint::TripPointType::kOneshotTempBelow;
} else if (index >= kRiseTripRange.first && index <= kRiseTripRange.second) {
return fuchsia_hardware_trippoint::TripPointType::kOneshotTempAbove;
}
__UNREACHABLE;
}
} // namespace temperature