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 
armada_ap806_thermal_read(u_register_t * temp)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 
armada_ap806_thermal_irq(void)82 static void armada_ap806_thermal_irq(void)
83 {
84 	/* Dummy read, register ROC */
85 	mmio_read_32(DFX_IRQ_CAUSE_REG);
86 }
87 
armada_ap806_thermal_overheat_irq_init(void)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 
armada_mc_to_reg_temp(unsigned int temp_mc)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 
armada_mc_to_reg_hyst(int hyst_mc)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 
armada_ap806_thermal_threshold(int thresh_mc,int hyst_mc)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 
armada_select_channel(int channel)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 
armada_ap806_thermal_init(void)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 
armada_is_valid(u_register_t * read)215 static void armada_is_valid(u_register_t *read)
216 {
217 	*read = (mmio_read_32(TSEN_STATUS) & TSEN_STATUS_VALID_MASK);
218 }
219 
mvebu_dfx_thermal_handle(u_register_t func,u_register_t * read,u_register_t x2,u_register_t x3)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