// 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 #include #include #include #include #include #include #include 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(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_buffer2(&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_buffer2(&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_buffer2(&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((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