/* * Copyright (C) 2019 Marvell International Ltd. * * SPDX-License-Identifier: BSD-3-Clause * https://spdx.org/licenses */ #include #include #include #include #include #include #include "dfx.h" /* #define DEBUG_DFX */ #ifdef DEBUG_DFX #define debug(format...) NOTICE(format) #else #define debug(format, arg...) #endif #define TSEN_CTRL0 0xf06f8084 #define TSEN_CTRL0_START BIT(0) #define TSEN_CTRL0_RESET BIT(1) #define TSEN_CTRL0_ENABLE BIT(2) #define TSEN_CTRL0_AVG_BYPASS BIT(6) #define TSEN_CTRL0_CHAN_SHIFT 13 #define TSEN_CTRL0_CHAN_MASK 0xF #define TSEN_CTRL0_OSR_SHIFT 24 #define TSEN_CTRL0_OSR_MAX 0x3 #define TSEN_CTRL0_MODE_SHIFT 30 #define TSEN_CTRL0_MODE_EXTERNAL 0x2U #define TSEN_CTRL0_MODE_MASK 0x3U #define TSEN_CTRL1 0xf06f8088 #define TSEN_CTRL1_INT_EN BIT(25) #define TSEN_CTRL1_HYST_SHIFT 19 #define TSEN_CTRL1_HYST_MASK (0x3 << TSEN_CTRL1_HYST_SHIFT) #define TSEN_CTRL1_THRESH_SHIFT 3 #define TSEN_CTRL1_THRESH_MASK (0x3ff << TSEN_CTRL1_THRESH_SHIFT) #define TSEN_STATUS 0xf06f808c #define TSEN_STATUS_VALID_OFFSET 16 #define TSEN_STATUS_VALID_MASK (0x1 << TSEN_STATUS_VALID_OFFSET) #define TSEN_STATUS_TEMP_OUT_OFFSET 0 #define TSEN_STATUS_TEMP_OUT_MASK (0x3FF << TSEN_STATUS_TEMP_OUT_OFFSET) #define DFX_SERVER_IRQ_SUM_MASK_REG 0xf06f8104 #define DFX_SERVER_IRQ_EN BIT(1) #define DFX_IRQ_CAUSE_REG 0xf06f8108 #define DFX_IRQ_MASK_REG 0xf06f810c #define DFX_IRQ_TSEN_OVERHEAT_OFFSET BIT(22) #define THERMAL_SEN_OUTPUT_MSB 512 #define THERMAL_SEN_OUTPUT_COMP 1024 #define COEF_M 423 #define COEF_B -150000LL static void armada_ap806_thermal_read(u_register_t *temp) { uint32_t reg; reg = mmio_read_32(TSEN_STATUS); reg = ((reg & TSEN_STATUS_TEMP_OUT_MASK) >> TSEN_STATUS_TEMP_OUT_OFFSET); /* * TSEN output format is signed as a 2s complement number * ranging from-512 to +511. when MSB is set, need to * calculate the complement number */ if (reg >= THERMAL_SEN_OUTPUT_MSB) reg -= THERMAL_SEN_OUTPUT_COMP; *temp = ((COEF_M * ((signed int)reg)) - COEF_B); } static void armada_ap806_thermal_irq(void) { /* Dummy read, register ROC */ mmio_read_32(DFX_IRQ_CAUSE_REG); } static void armada_ap806_thermal_overheat_irq_init(void) { uint32_t reg; /* Clear DFX temperature IRQ cause */ reg = mmio_read_32(DFX_IRQ_CAUSE_REG); /* Enable DFX Temperature IRQ */ reg = mmio_read_32(DFX_IRQ_MASK_REG); reg |= DFX_IRQ_TSEN_OVERHEAT_OFFSET; mmio_write_32(DFX_IRQ_MASK_REG, reg); /* Enable DFX server IRQ */ reg = mmio_read_32(DFX_SERVER_IRQ_SUM_MASK_REG); reg |= DFX_SERVER_IRQ_EN; mmio_write_32(DFX_SERVER_IRQ_SUM_MASK_REG, reg); /* Enable overheat interrupt */ reg = mmio_read_32(TSEN_CTRL1); reg |= TSEN_CTRL1_INT_EN; mmio_write_32(TSEN_CTRL1, reg); } static unsigned int armada_mc_to_reg_temp(unsigned int temp_mc) { unsigned int sample; sample = (temp_mc + COEF_B) / COEF_M; return sample & 0x3ff; } /* * The documentation states: * high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2) * which is the mathematical derivation for: * 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C */ static unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200}; static unsigned int armada_mc_to_reg_hyst(int hyst_mc) { int i; /* * We will always take the smallest possible hysteresis to avoid risking * the hardware integrity by enlarging the threshold by +8°C in the * worst case. */ for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--) if (hyst_mc >= hyst_levels_mc[i]) break; return i; } static void armada_ap806_thermal_threshold(int thresh_mc, int hyst_mc) { uint32_t ctrl1; unsigned int threshold = armada_mc_to_reg_temp(thresh_mc); unsigned int hysteresis = armada_mc_to_reg_hyst(hyst_mc); ctrl1 = mmio_read_32(TSEN_CTRL1); /* Set Threshold */ if (thresh_mc >= 0) { ctrl1 &= ~(TSEN_CTRL1_THRESH_MASK); ctrl1 |= threshold << TSEN_CTRL1_THRESH_SHIFT; } /* Set Hysteresis */ if (hyst_mc >= 0) { ctrl1 &= ~(TSEN_CTRL1_HYST_MASK); ctrl1 |= hysteresis << TSEN_CTRL1_HYST_SHIFT; } mmio_write_32(TSEN_CTRL1, ctrl1); } static void armada_select_channel(int channel) { uint32_t ctrl0; /* Stop the measurements */ ctrl0 = mmio_read_32(TSEN_CTRL0); ctrl0 &= ~TSEN_CTRL0_START; mmio_write_32(TSEN_CTRL0, ctrl0); /* Reset the mode, internal sensor will be automatically selected */ ctrl0 &= ~(TSEN_CTRL0_MODE_MASK << TSEN_CTRL0_MODE_SHIFT); /* Other channels are external and should be selected accordingly */ if (channel) { /* Change the mode to external */ ctrl0 |= TSEN_CTRL0_MODE_EXTERNAL << TSEN_CTRL0_MODE_SHIFT; /* Select the sensor */ ctrl0 &= ~(TSEN_CTRL0_CHAN_MASK << TSEN_CTRL0_CHAN_SHIFT); ctrl0 |= (channel - 1) << TSEN_CTRL0_CHAN_SHIFT; } /* Actually set the mode/channel */ mmio_write_32(TSEN_CTRL0, ctrl0); /* Re-start the measurements */ ctrl0 |= TSEN_CTRL0_START; mmio_write_32(TSEN_CTRL0, ctrl0); } static void armada_ap806_thermal_init(void) { uint32_t reg; reg = mmio_read_32(TSEN_CTRL0); reg &= ~TSEN_CTRL0_RESET; reg |= TSEN_CTRL0_START | TSEN_CTRL0_ENABLE; /* Sample every ~2ms */ reg |= TSEN_CTRL0_OSR_MAX << TSEN_CTRL0_OSR_SHIFT; /* Enable average (2 samples by default) */ reg &= ~TSEN_CTRL0_AVG_BYPASS; mmio_write_32(TSEN_CTRL0, reg); debug("thermal: Initialization done\n"); } static void armada_is_valid(u_register_t *read) { *read = (mmio_read_32(TSEN_STATUS) & TSEN_STATUS_VALID_MASK); } int mvebu_dfx_thermal_handle(u_register_t func, u_register_t *read, u_register_t x2, u_register_t x3) { debug_enter(); switch (func) { case MV_SIP_DFX_THERMAL_INIT: armada_ap806_thermal_init(); break; case MV_SIP_DFX_THERMAL_READ: armada_ap806_thermal_read(read); break; case MV_SIP_DFX_THERMAL_IRQ: armada_ap806_thermal_irq(); break; case MV_SIP_DFX_THERMAL_THRESH: armada_ap806_thermal_threshold(x2, x3); armada_ap806_thermal_overheat_irq_init(); break; case MV_SIP_DFX_THERMAL_IS_VALID: armada_is_valid(read); break; case MV_SIP_DFX_THERMAL_SEL_CHANNEL: armada_select_channel(x2); break; default: ERROR("unsupported dfx func\n"); return -EINVAL; } debug_exit(); return 0; }