blob: 891d607aea39c0bab5ba70b041240b29f921bad5 [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 "aml-tsensor-regs.h"
#include <ddk/debug.h>
#include <fbl/auto_call.h>
#include <fbl/unique_ptr.h>
#include <hw/reg.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/syscalls/port.h>
namespace thermal {
namespace {
// MMIO indexes.
constexpr uint32_t kPllMmio = 0;
constexpr uint32_t kAoMmio = 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;
constexpr uint32_t kRebootTemp = 130000;
} // 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\n", __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\n");
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\n");
return status;
}
}
return status;
}
zx_status_t AmlTSensor::InitTripPoints() {
auto set_thresholds = [this](auto&& rise_threshold, auto&& fall_threshold, uint32_t i) {
auto rise_temperature_0 = TempToCode(
thermal_config_.trip_point_info[i].up_temp,
true);
auto rise_temperature_1 = TempToCode(
thermal_config_.trip_point_info[i + 1].up_temp,
true);
auto fall_temperature_0 = TempToCode(
thermal_config_.trip_point_info[i].down_temp,
false);
auto fall_temperature_1 = TempToCode(
thermal_config_.trip_point_info[i + 1].down_temp,
false);
// Program the 2 rise temperature thresholds.
rise_threshold
.ReadFrom(&*pll_mmio_)
.set_rise_th0(rise_temperature_0)
.set_rise_th1(rise_temperature_1)
.WriteTo(&*pll_mmio_);
// Program the 2 fall temperature thresholds.
fall_threshold
.ReadFrom(&*pll_mmio_)
.set_fall_th0(fall_temperature_0)
.set_fall_th1(fall_temperature_1)
.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.
set_thresholds(TsCfgReg4::Get(), TsCfgReg6::Get(), 1);
set_thresholds(TsCfgReg5::Get(), TsCfgReg7::Get(), 3);
// 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.
TsCfgReg1::Get()
.ReadFrom(&*pll_mmio_)
.set_rise_th3_irq_en(1)
.set_rise_th2_irq_en(1)
.set_rise_th1_irq_en(1)
.set_rise_th0_irq_en(1)
.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);
int rc = thrd_create_with_name(&irq_thread_,
start_thread,
this,
"aml_tsendor_irq_thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t AmlTSensor::InitPdev(zx_device_t* parent) {
zx_status_t status = device_get_protocol(parent,
ZX_PROTOCOL_PDEV,
&pdev_);
if (status != ZX_OK) {
return status;
}
// Map amlogic temperature sensopr peripheral control registers.
mmio_buffer_t mmio;
status = pdev_map_mmio_buffer(&pdev_, kPllMmio, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map periph mmio: %d\n", status);
return status;
}
pll_mmio_ = ddk::MmioBuffer(mmio);
status = pdev_map_mmio_buffer(&pdev_, kAoMmio, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map periph mmio: %d\n", status);
return status;
}
ao_mmio_ = ddk::MmioBuffer(mmio);
status = pdev_map_mmio_buffer(&pdev_, kHiuMmio, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map periph mmio: %d\n", status);
return status;
}
hiu_mmio_ = ddk::MmioBuffer(mmio);
// Map tsensor interrupt.
status = pdev_map_interrupt(&pdev_, 0, tsensor_irq_.reset_and_get_address());
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: could not map tsensor interrupt\n");
return status;
}
return ZX_OK;
}
// Tsensor treats temperature as a mapped temperature code.
// The temperature is converted differently depending on the calibration type.
uint32_t AmlTSensor::TempToCode(uint32_t temp, bool trend) {
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 * 10 + kCalC_) / kCalD_ +
(1 << 16) * (uefuse & 0x7fff) / (1 << 16));
} else {
sensor_code = ((1 << 16) * (temp * 10 + 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.
uint32_t AmlTSensor::CodeToTemp(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 = (1000 * ((sensor_temp - (uefuse & (0x7fff))) * kCalD_ / (1 << 16) - kCalC_) / 10);
} else {
sensor_temp = 1000 * ((sensor_temp + uefuse) * kCalD_ / (1 << 16) - kCalC_) / 10;
}
return sensor_temp;
}
uint32_t AmlTSensor::ReadTemperature() {
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 CodeToTemp(value_all / count) / MCELSIUS;
}
}
void AmlTSensor::SetRebootTemperature(uint32_t temp) {
uint32_t reboot_val = TempToCode(kRebootTemp / MCELSIUS, 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::InitSensor(zx_device_t* parent, thermal_device_info_t thermal_config) {
zx_status_t status = InitPdev(parent);
if (status != ZX_OK) {
return status;
}
// Copy the thermal_config
memcpy(&thermal_config_, &thermal_config, sizeof(thermal_device_info_t));
// Get the trim info.
trim_info_ = ao_mmio_->Read32(AML_TRIM_INFO);
// 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.
status = zx_port_create(0, &port_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-tsensor: Unable to create port\n");
return status;
}
// Setup IRQ's and rise/fall thresholds.
return InitTripPoints();
}
AmlTSensor::~AmlTSensor() {
running_.store(false);
thrd_join(irq_thread_, NULL);
tsensor_irq_.destroy();
}
} // namespace thermal