1 /* 2 * Copyright (C) 2019 Marvell International Ltd. 3 * 4 * SPDX-License-Identifier: BSD-3-Clause 5 * https://spdx.org/licenses 6 */ 7 #include <common/debug.h> 8 #include <drivers/delay_timer.h> 9 #include <errno.h> 10 #include <lib/mmio.h> 11 #include <mvebu.h> 12 #include <stdbool.h> 13 #include "dfx.h" 14 15 /* #define DEBUG_DFX */ 16 #ifdef DEBUG_DFX 17 #define debug(format...) NOTICE(format) 18 #else 19 #define debug(format, arg...) 20 #endif 21 22 #define TSEN_CTRL0 0xf06f8084 23 #define TSEN_CTRL0_START BIT(0) 24 #define TSEN_CTRL0_RESET BIT(1) 25 #define TSEN_CTRL0_ENABLE BIT(2) 26 #define TSEN_CTRL0_AVG_BYPASS BIT(6) 27 #define TSEN_CTRL0_CHAN_SHIFT 13 28 #define TSEN_CTRL0_CHAN_MASK 0xF 29 #define TSEN_CTRL0_OSR_SHIFT 24 30 #define TSEN_CTRL0_OSR_MAX 0x3 31 #define TSEN_CTRL0_MODE_SHIFT 30 32 #define TSEN_CTRL0_MODE_EXTERNAL 0x2U 33 #define TSEN_CTRL0_MODE_MASK 0x3U 34 35 #define TSEN_CTRL1 0xf06f8088 36 #define TSEN_CTRL1_INT_EN BIT(25) 37 #define TSEN_CTRL1_HYST_SHIFT 19 38 #define TSEN_CTRL1_HYST_MASK (0x3 << TSEN_CTRL1_HYST_SHIFT) 39 #define TSEN_CTRL1_THRESH_SHIFT 3 40 #define TSEN_CTRL1_THRESH_MASK (0x3ff << TSEN_CTRL1_THRESH_SHIFT) 41 42 #define TSEN_STATUS 0xf06f808c 43 #define TSEN_STATUS_VALID_OFFSET 16 44 #define TSEN_STATUS_VALID_MASK (0x1 << TSEN_STATUS_VALID_OFFSET) 45 #define TSEN_STATUS_TEMP_OUT_OFFSET 0 46 #define TSEN_STATUS_TEMP_OUT_MASK (0x3FF << TSEN_STATUS_TEMP_OUT_OFFSET) 47 48 #define DFX_SERVER_IRQ_SUM_MASK_REG 0xf06f8104 49 #define DFX_SERVER_IRQ_EN BIT(1) 50 51 #define DFX_IRQ_CAUSE_REG 0xf06f8108 52 53 #define DFX_IRQ_MASK_REG 0xf06f810c 54 #define DFX_IRQ_TSEN_OVERHEAT_OFFSET BIT(22) 55 56 #define THERMAL_SEN_OUTPUT_MSB 512 57 #define THERMAL_SEN_OUTPUT_COMP 1024 58 59 #define COEF_M 423 60 #define COEF_B -150000LL 61 62 static void armada_ap806_thermal_read(u_register_t *temp) 63 { 64 uint32_t reg; 65 66 reg = mmio_read_32(TSEN_STATUS); 67 68 reg = ((reg & TSEN_STATUS_TEMP_OUT_MASK) >> 69 TSEN_STATUS_TEMP_OUT_OFFSET); 70 71 /* 72 * TSEN output format is signed as a 2s complement number 73 * ranging from-512 to +511. when MSB is set, need to 74 * calculate the complement number 75 */ 76 if (reg >= THERMAL_SEN_OUTPUT_MSB) 77 reg -= THERMAL_SEN_OUTPUT_COMP; 78 79 *temp = ((COEF_M * ((signed int)reg)) - COEF_B); 80 } 81 82 static void armada_ap806_thermal_irq(void) 83 { 84 /* Dummy read, register ROC */ 85 mmio_read_32(DFX_IRQ_CAUSE_REG); 86 } 87 88 static void armada_ap806_thermal_overheat_irq_init(void) 89 { 90 uint32_t reg; 91 92 /* Clear DFX temperature IRQ cause */ 93 reg = mmio_read_32(DFX_IRQ_CAUSE_REG); 94 95 /* Enable DFX Temperature IRQ */ 96 reg = mmio_read_32(DFX_IRQ_MASK_REG); 97 reg |= DFX_IRQ_TSEN_OVERHEAT_OFFSET; 98 mmio_write_32(DFX_IRQ_MASK_REG, reg); 99 100 /* Enable DFX server IRQ */ 101 reg = mmio_read_32(DFX_SERVER_IRQ_SUM_MASK_REG); 102 reg |= DFX_SERVER_IRQ_EN; 103 mmio_write_32(DFX_SERVER_IRQ_SUM_MASK_REG, reg); 104 105 /* Enable overheat interrupt */ 106 reg = mmio_read_32(TSEN_CTRL1); 107 reg |= TSEN_CTRL1_INT_EN; 108 mmio_write_32(TSEN_CTRL1, reg); 109 } 110 111 static unsigned int armada_mc_to_reg_temp(unsigned int temp_mc) 112 { 113 unsigned int sample; 114 115 sample = (temp_mc + COEF_B) / COEF_M; 116 117 return sample & 0x3ff; 118 } 119 120 /* 121 * The documentation states: 122 * high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2) 123 * which is the mathematical derivation for: 124 * 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C 125 */ 126 static unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200}; 127 128 static unsigned int armada_mc_to_reg_hyst(int hyst_mc) 129 { 130 int i; 131 132 /* 133 * We will always take the smallest possible hysteresis to avoid risking 134 * the hardware integrity by enlarging the threshold by +8°C in the 135 * worst case. 136 */ 137 for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--) 138 if (hyst_mc >= hyst_levels_mc[i]) 139 break; 140 141 return i; 142 } 143 144 static void armada_ap806_thermal_threshold(int thresh_mc, int hyst_mc) 145 { 146 uint32_t ctrl1; 147 unsigned int threshold = armada_mc_to_reg_temp(thresh_mc); 148 unsigned int hysteresis = armada_mc_to_reg_hyst(hyst_mc); 149 150 ctrl1 = mmio_read_32(TSEN_CTRL1); 151 /* Set Threshold */ 152 if (thresh_mc >= 0) { 153 ctrl1 &= ~(TSEN_CTRL1_THRESH_MASK); 154 ctrl1 |= threshold << TSEN_CTRL1_THRESH_SHIFT; 155 } 156 157 /* Set Hysteresis */ 158 if (hyst_mc >= 0) { 159 ctrl1 &= ~(TSEN_CTRL1_HYST_MASK); 160 ctrl1 |= hysteresis << TSEN_CTRL1_HYST_SHIFT; 161 } 162 163 mmio_write_32(TSEN_CTRL1, ctrl1); 164 } 165 166 static void armada_select_channel(int channel) 167 { 168 uint32_t ctrl0; 169 170 /* Stop the measurements */ 171 ctrl0 = mmio_read_32(TSEN_CTRL0); 172 ctrl0 &= ~TSEN_CTRL0_START; 173 mmio_write_32(TSEN_CTRL0, ctrl0); 174 175 /* Reset the mode, internal sensor will be automatically selected */ 176 ctrl0 &= ~(TSEN_CTRL0_MODE_MASK << TSEN_CTRL0_MODE_SHIFT); 177 178 /* Other channels are external and should be selected accordingly */ 179 if (channel) { 180 /* Change the mode to external */ 181 ctrl0 |= TSEN_CTRL0_MODE_EXTERNAL << 182 TSEN_CTRL0_MODE_SHIFT; 183 /* Select the sensor */ 184 ctrl0 &= ~(TSEN_CTRL0_CHAN_MASK << TSEN_CTRL0_CHAN_SHIFT); 185 ctrl0 |= (channel - 1) << TSEN_CTRL0_CHAN_SHIFT; 186 } 187 188 /* Actually set the mode/channel */ 189 mmio_write_32(TSEN_CTRL0, ctrl0); 190 191 /* Re-start the measurements */ 192 ctrl0 |= TSEN_CTRL0_START; 193 mmio_write_32(TSEN_CTRL0, ctrl0); 194 } 195 196 static void armada_ap806_thermal_init(void) 197 { 198 uint32_t reg; 199 200 reg = mmio_read_32(TSEN_CTRL0); 201 reg &= ~TSEN_CTRL0_RESET; 202 reg |= TSEN_CTRL0_START | TSEN_CTRL0_ENABLE; 203 204 /* Sample every ~2ms */ 205 reg |= TSEN_CTRL0_OSR_MAX << TSEN_CTRL0_OSR_SHIFT; 206 207 /* Enable average (2 samples by default) */ 208 reg &= ~TSEN_CTRL0_AVG_BYPASS; 209 210 mmio_write_32(TSEN_CTRL0, reg); 211 212 debug("thermal: Initialization done\n"); 213 } 214 215 static void armada_is_valid(u_register_t *read) 216 { 217 *read = (mmio_read_32(TSEN_STATUS) & TSEN_STATUS_VALID_MASK); 218 } 219 220 int mvebu_dfx_thermal_handle(u_register_t func, u_register_t *read, 221 u_register_t x2, u_register_t x3) 222 { 223 debug_enter(); 224 225 switch (func) { 226 case MV_SIP_DFX_THERMAL_INIT: 227 armada_ap806_thermal_init(); 228 break; 229 case MV_SIP_DFX_THERMAL_READ: 230 armada_ap806_thermal_read(read); 231 break; 232 case MV_SIP_DFX_THERMAL_IRQ: 233 armada_ap806_thermal_irq(); 234 break; 235 case MV_SIP_DFX_THERMAL_THRESH: 236 armada_ap806_thermal_threshold(x2, x3); 237 armada_ap806_thermal_overheat_irq_init(); 238 break; 239 case MV_SIP_DFX_THERMAL_IS_VALID: 240 armada_is_valid(read); 241 break; 242 case MV_SIP_DFX_THERMAL_SEL_CHANNEL: 243 armada_select_channel(x2); 244 break; 245 default: 246 ERROR("unsupported dfx func\n"); 247 return -EINVAL; 248 } 249 250 debug_exit(); 251 252 return 0; 253 } 254