blob: 0ee436a8637dd3c9696433690cdff0f011b72d13 [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 "aml-tsensor.h"
#include <lib/ddk/debug.h>
#include <lib/ddk/hw/reg.h>
#include <lib/device-protocol/pdev.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/syscalls/port.h>
#include <zircon/types.h>
#include <cmath>
#include "aml-tsensor-regs.h"
namespace thermal {
namespace {
// MMIO indexes.
constexpr uint32_t kPllMmio = 0;
constexpr uint32_t kTrimMmio = 1;
constexpr uint32_t kHiuMmio = 2;
// Thermal calibration magic numbers from uboot.
constexpr int32_t kCalA_ = 324;
constexpr int32_t kCalB_ = 424;
constexpr int32_t kCalC_ = 3159;
constexpr int32_t kCalD_ = 9411;
} // namespace
zx_status_t AmlTSensor::NotifyThermalDaemon() {
zx_port_packet_t thermal_port_packet;
thermal_port_packet.key = current_trip_idx_;
thermal_port_packet.type = ZX_PKT_TYPE_USER;
return zx_port_queue(port_, &thermal_port_packet);
}
void AmlTSensor::UpdateRiseThresholdIrq(uint32_t irq) {
// Clear the IRQ.
auto sensor_ctl = TsCfgReg1::Get().ReadFrom(&*pll_mmio_);
auto reg_value = sensor_ctl.reg_value();
// Disable the IRQ
reg_value &= ~(1 << (IRQ_RISE_ENABLE_SHIFT + irq));
// Enable corresponding Fall IRQ
reg_value |= (1 << (IRQ_FALL_ENABLE_SHIFT + irq));
// Clear Rise IRQ Stat.
reg_value |= (1 << (IRQ_RISE_STAT_CLR_SHIFT + irq));
sensor_ctl.set_reg_value(reg_value);
sensor_ctl.WriteTo(&*pll_mmio_);
// Write 0 to CLR_STAT bit.
sensor_ctl = TsCfgReg1::Get().ReadFrom(&*pll_mmio_);
reg_value = sensor_ctl.reg_value();
reg_value &= ~(1 << (IRQ_RISE_STAT_CLR_SHIFT + irq));
sensor_ctl.set_reg_value(reg_value);
sensor_ctl.WriteTo(&*pll_mmio_);
}
void AmlTSensor::UpdateFallThresholdIrq(uint32_t irq) {
// Clear the IRQ.
auto sensor_ctl = TsCfgReg1::Get().ReadFrom(&*pll_mmio_);
auto reg_value = sensor_ctl.reg_value();
// Disable the IRQ
reg_value &= ~(1 << (IRQ_FALL_ENABLE_SHIFT + irq));
// Enable corresponding Rise IRQ
reg_value |= (1 << (IRQ_RISE_ENABLE_SHIFT + irq));
// Clear Fall IRQ Stat.
reg_value |= (1 << (IRQ_FALL_STAT_CLR_SHIFT + irq));
sensor_ctl.set_reg_value(reg_value);
sensor_ctl.WriteTo(&*pll_mmio_);
// Write 0 to CLR_STAT bit.
sensor_ctl = TsCfgReg1::Get().ReadFrom(&*pll_mmio_);
reg_value = sensor_ctl.reg_value();
reg_value &= ~(1 << (IRQ_FALL_STAT_CLR_SHIFT + irq));
sensor_ctl.set_reg_value(reg_value);
sensor_ctl.WriteTo(&*pll_mmio_);
}
int AmlTSensor::TripPointIrqHandler() {
zxlogf(INFO, "%s start", __func__);
zx_status_t status = ZX_OK;
// Notify thermal daemon about the default settings.
status = NotifyThermalDaemon();
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: Failed to send packet via port");
return status;
}
while (running_.load()) {
status = tsensor_irq_.wait(NULL);
if (status != ZX_OK) {
return status;
}
auto irq_stat = TsStat1::Get().ReadFrom(&*pll_mmio_);
if (irq_stat.reg_value() & AML_RISE_THRESHOLD_IRQ) {
// Handle Rise threshold IRQs.
if (irq_stat.rise_th3_irq()) {
UpdateRiseThresholdIrq(3);
current_trip_idx_ = 4;
} else if (irq_stat.rise_th2_irq()) {
UpdateRiseThresholdIrq(2);
current_trip_idx_ = 3;
} else if (irq_stat.rise_th1_irq()) {
UpdateRiseThresholdIrq(1);
current_trip_idx_ = 2;
} else if (irq_stat.rise_th0_irq()) {
UpdateRiseThresholdIrq(0);
current_trip_idx_ = 1;
}
} else if (irq_stat.reg_value() & AML_FALL_THRESHOLD_IRQ) {
// Handle Fall threshold IRQs.
if (irq_stat.fall_th3_irq()) {
UpdateFallThresholdIrq(3);
current_trip_idx_ = 3;
} else if (irq_stat.fall_th2_irq()) {
UpdateFallThresholdIrq(2);
current_trip_idx_ = 2;
} else if (irq_stat.fall_th1_irq()) {
UpdateFallThresholdIrq(1);
current_trip_idx_ = 1;
} else if (irq_stat.fall_th0_irq()) {
UpdateFallThresholdIrq(0);
current_trip_idx_ = 0;
}
} else {
// Spurious interrupt
continue;
}
// Notify thermal daemon about new trip point.
status = NotifyThermalDaemon();
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: Failed to send packet via port");
return status;
}
}
return status;
}
zx_status_t AmlTSensor::InitTripPoints() {
if (thermal_config_.trip_point_info[thermal_config_.num_trip_points].up_temp_celsius !=
(-273.15f + 2.0f)) {
return ZX_ERR_INTERNAL;
}
auto set_thresholds = [this](auto&& rise_threshold, auto&& fall_threshold, uint32_t i) {
auto rise_temperature =
TempCelsiusToCode(thermal_config_.trip_point_info[i].up_temp_celsius, true);
auto fall_temperature =
TempCelsiusToCode(thermal_config_.trip_point_info[i].down_temp_celsius, false);
// Program the 2 rise temperature thresholds.
if (i % 2) {
rise_threshold.ReadFrom(&*pll_mmio_).set_rise_th0(rise_temperature).WriteTo(&*pll_mmio_);
fall_threshold.ReadFrom(&*pll_mmio_).set_fall_th0(fall_temperature).WriteTo(&*pll_mmio_);
} else {
rise_threshold.ReadFrom(&*pll_mmio_).set_rise_th1(rise_temperature).WriteTo(&*pll_mmio_);
fall_threshold.ReadFrom(&*pll_mmio_).set_fall_th1(fall_temperature).WriteTo(&*pll_mmio_);
}
};
// Set rise and fall trip points for the first 4 trip points, since the HW supports only 4.
// We skip the 1st entry since it's the default setting for boot up.
switch (thermal_config_.num_trip_points) {
default:
case 5:
set_thresholds(TsCfgReg5::Get(), TsCfgReg7::Get(), 4);
[[fallthrough]];
case 4:
set_thresholds(TsCfgReg5::Get(), TsCfgReg7::Get(), 3);
[[fallthrough]];
case 3:
set_thresholds(TsCfgReg4::Get(), TsCfgReg6::Get(), 2);
[[fallthrough]];
case 2:
set_thresholds(TsCfgReg4::Get(), TsCfgReg6::Get(), 1);
[[fallthrough]];
case 1:
[[fallthrough]];
case 0:
break;
}
// Clear all IRQ's status.
TsCfgReg1::Get()
.ReadFrom(&*pll_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(&*pll_mmio_);
TsCfgReg1::Get()
.ReadFrom(&*pll_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(&*pll_mmio_);
// Enable all IRQs.
auto ts_cfg_reg1 = TsCfgReg1::Get().ReadFrom(&*pll_mmio_);
switch (thermal_config_.num_trip_points) {
default:
case 5:
ts_cfg_reg1.set_rise_th3_irq_en(1);
[[fallthrough]];
case 4:
ts_cfg_reg1.set_rise_th2_irq_en(1);
[[fallthrough]];
case 3:
ts_cfg_reg1.set_rise_th1_irq_en(1);
[[fallthrough]];
case 2:
ts_cfg_reg1.set_rise_th0_irq_en(1);
[[fallthrough]];
case 1:
case 0:
break;
}
ts_cfg_reg1.set_enable_irq(1).WriteTo(&*pll_mmio_);
// Start thermal notification thread.
auto start_thread = [](void* arg) -> int {
return static_cast<AmlTSensor*>(arg)->TripPointIrqHandler();
};
running_.store(true);
thrd_t irq_thread;
int rc = thrd_create_with_name(&irq_thread, start_thread, this, "aml_tsendor_irq_thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
irq_thread_ = irq_thread;
return ZX_OK;
}
// Tsensor treats temperature as a mapped temperature code.
// The temperature is converted differently depending on the calibration type.
uint32_t AmlTSensor::TempCelsiusToCode(float temp_c, bool trend) {
int32_t temp_decicelsius = static_cast<int32_t>(std::round(temp_c * 10.0f));
int64_t sensor_code;
uint32_t reg_code;
uint32_t uefuse = trim_info_ & 0xffff;
// Referred u-boot code for below magic calculations.
// T = 727.8*(u_real+u_efuse/(1<<16)) - 274.7
// u_readl = (5.05*YOUT)/((1<<16)+ 4.05*YOUT)
// u_readl = (T + 274.7) / 727.8 - u_efuse / (1 << 16)
// Yout = (u_readl / (5.05 - 4.05u_readl)) *(1 << 16)
if (uefuse & 0x8000) {
sensor_code = ((1 << 16) * (temp_decicelsius + kCalC_) / kCalD_ +
(1 << 16) * (uefuse & 0x7fff) / (1 << 16));
} else {
sensor_code = ((1 << 16) * (temp_decicelsius + kCalC_) / kCalD_ -
(1 << 16) * (uefuse & 0x7fff) / (1 << 16));
}
sensor_code = (sensor_code * 100 / (kCalB_ - kCalA_ * sensor_code / (1 << 16)));
if (trend) {
reg_code = static_cast<uint32_t>((sensor_code >> 0x4) & AML_TS_TEMP_MASK) + AML_TEMP_CAL;
} else {
reg_code = ((sensor_code >> 0x4) & AML_TS_TEMP_MASK);
}
return reg_code;
}
// Calculate a temperature value from a temperature code.
// The unit of the temperature is degree Celsius.
float AmlTSensor::CodeToTempCelsius(uint32_t temp_code) {
uint32_t sensor_temp = temp_code;
uint32_t uefuse = trim_info_ & 0xffff;
// Referred u-boot code for below magic calculations.
// T = 727.8*(u_real+u_efuse/(1<<16)) - 274.7
// u_readl = (5.05*YOUT)/((1<<16)+ 4.05*YOUT)
sensor_temp =
((sensor_temp * kCalB_) / 100 * (1 << 16) / (1 * (1 << 16) + kCalA_ * sensor_temp / 100));
if (uefuse & 0x8000) {
sensor_temp = ((sensor_temp - (uefuse & (0x7fff))) * kCalD_ / (1 << 16) - kCalC_);
} else {
sensor_temp = ((sensor_temp + uefuse) * kCalD_ / (1 << 16) - kCalC_);
}
return static_cast<float>(sensor_temp) / 10.0f;
}
float AmlTSensor::ReadTemperatureCelsius() {
int count = 0;
unsigned int value_all = 0;
// Datasheet is incorrect.
// Referred to u-boot code.
// Yay magic numbers.
for (int j = 0; j < AML_TS_VALUE_CONT; j++) {
auto ts_stat0 = TsStat0::Get().ReadFrom(&*pll_mmio_);
auto tvalue = ts_stat0.temperature();
if ((tvalue >= 0x18a9) && (tvalue <= 0x32a6)) {
count++;
value_all += tvalue;
}
}
if (count == 0) {
return 0;
} else {
return CodeToTempCelsius(value_all / count);
}
}
void AmlTSensor::SetRebootTemperatureCelsius(float temp_c) {
uint32_t reboot_val = TempCelsiusToCode(temp_c, true);
auto reboot_config = TsCfgReg2::Get().ReadFrom(&*pll_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(&*pll_mmio_);
}
zx_status_t AmlTSensor::GetStateChangePort(zx_handle_t* port) {
return zx_handle_duplicate(port_, ZX_RIGHT_SAME_RIGHTS, port);
}
zx_status_t AmlTSensor::Create(zx_device_t* parent,
fuchsia_hardware_thermal_ThermalDeviceInfo thermal_config) {
auto pdev = ddk::PDev::FromFragment(parent);
if (!pdev.is_valid()) {
zxlogf(ERROR, "aml-voltage: failed to get pdev protocol");
return ZX_ERR_NOT_SUPPORTED;
}
// Map amlogic temperature sensor peripheral control registers.
zx_status_t status = pdev.MapMmio(kPllMmio, &pll_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map periph mmio: %d", status);
return status;
}
status = pdev.MapMmio(kTrimMmio, &trim_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map periph mmio: %d", status);
return status;
}
status = pdev.MapMmio(kHiuMmio, &hiu_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map periph mmio: %d", status);
return status;
}
// Map tsensor interrupt.
status = pdev.GetInterrupt(0, 0, &tsensor_irq_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map tsensor interrupt");
return status;
}
return InitSensor(thermal_config);
}
zx_status_t AmlTSensor::InitSensor(fuchsia_hardware_thermal_ThermalDeviceInfo thermal_config) {
// Copy the thermal_config
memcpy(&thermal_config_, &thermal_config, sizeof(fuchsia_hardware_thermal_ThermalDeviceInfo));
// Get the trim info.
trim_info_ = trim_mmio_->Read32(0);
// Set the clk.
hiu_mmio_->Write32(AML_HHI_TS_CLK_ENABLE, AML_HHI_TS_CLK_CNTL);
// Not setting IRQ's here.
auto sensor_ctl = TsCfgReg1::Get().ReadFrom(&*pll_mmio_);
sensor_ctl.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(&*pll_mmio_);
// Create a port to send messages to thermal daemon.
zx_status_t status = zx_port_create(0, &port_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: Unable to create port");
return status;
}
// Configure the SoC reset temperature.
if (thermal_config.critical_temp_celsius > 0.0) {
SetRebootTemperatureCelsius(thermal_config.critical_temp_celsius);
}
// Setup IRQ's and rise/fall thresholds.
return InitTripPoints();
}
AmlTSensor::~AmlTSensor() {
running_.store(false);
tsensor_irq_.destroy();
if (irq_thread_) {
thrd_join(*irq_thread_, NULL);
}
}
} // namespace thermal