summaryrefslogtreecommitdiff
path: root/drivers/thermal/imx_thermal.c
blob: e50b85bd594ef447396c51586e47edd1064e5cdf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// SPDX-License-Identifier: GPL-2.0+
/*
 * (C) Copyright 2014 Freescale Semiconductor, Inc.
 * Author: Nitin Garg <nitin.garg@freescale.com>
 *             Ye Li <Ye.Li@freescale.com>
 */

#include <config.h>
#include <common.h>
#include <div64.h>
#include <fuse.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/sys_proto.h>
#include <dm.h>
#include <errno.h>
#include <malloc.h>
#include <linux/math64.h>
#include <thermal.h>
#include <imx_thermal.h>

/* board will busyloop until this many degrees C below CPU max temperature */
#define TEMPERATURE_HOT_DELTA   5 /* CPU maxT - 5C */
#define FACTOR0			10000000
#define FACTOR1			15423
#define FACTOR2			4148468
#define OFFSET			3580661
#define MEASURE_FREQ		327
#define TEMPERATURE_MIN         -40
#define TEMPERATURE_HOT         85
#define TEMPERATURE_MAX         125

#define TEMPSENSE0_TEMP_CNT_SHIFT	8
#define TEMPSENSE0_TEMP_CNT_MASK	(0xfff << TEMPSENSE0_TEMP_CNT_SHIFT)
#define TEMPSENSE0_FINISHED		(1 << 2)
#define TEMPSENSE0_MEASURE_TEMP		(1 << 1)
#define TEMPSENSE0_POWER_DOWN		(1 << 0)
#define MISC0_REFTOP_SELBIASOFF		(1 << 3)
#define TEMPSENSE1_MEASURE_FREQ		0xffff

struct thermal_data {
	unsigned int fuse;
	int critical;
	int minc;
	int maxc;
};

#if defined(CONFIG_MX6)
static int read_cpu_temperature(struct udevice *dev)
{
	int temperature;
	unsigned int reg, n_meas;
	const struct imx_thermal_plat *pdata = dev_get_platdata(dev);
	struct anatop_regs *anatop = (struct anatop_regs *)pdata->regs;
	struct thermal_data *priv = dev_get_priv(dev);
	u32 fuse = priv->fuse;
	int t1, n1;
	s64 c1, c2;
	s64 temp64;
	s32 rem;

	/*
	 * Sensor data layout:
	 *   [31:20] - sensor value @ 25C
	 * We use universal formula now and only need sensor value @ 25C
	 * slope = 0.4445388 - (0.0016549 * 25C fuse)
	 */
	n1 = fuse >> 20;
	t1 = 25; /* t1 always 25C */

	/*
	 * Derived from linear interpolation:
	 * slope = 0.4445388 - (0.0016549 * 25C fuse)
	 * slope = (FACTOR2 - FACTOR1 * n1) / FACTOR0
	 * offset = 3.580661
	 * offset = OFFSET / 1000000
	 * (Nmeas - n1) / (Tmeas - t1 - offset) = slope
	 * We want to reduce this down to the minimum computation necessary
	 * for each temperature read.  Also, we want Tmeas in millicelsius
	 * and we don't want to lose precision from integer division. So...
	 * Tmeas = (Nmeas - n1) / slope + t1 + offset
	 * milli_Tmeas = 1000000 * (Nmeas - n1) / slope + 1000000 * t1 + OFFSET
	 * milli_Tmeas = -1000000 * (n1 - Nmeas) / slope + 1000000 * t1 + OFFSET
	 * Let constant c1 = (-1000000 / slope)
	 * milli_Tmeas = (n1 - Nmeas) * c1 + 1000000 * t1 + OFFSET
	 * Let constant c2 = n1 *c1 + 1000000 * t1
	 * milli_Tmeas = (c2 - Nmeas * c1) + OFFSET
	 * Tmeas = ((c2 - Nmeas * c1) + OFFSET) / 1000000
	 */
	temp64 = FACTOR0;
	temp64 *= 1000000;
	temp64 = div_s64_rem(temp64, FACTOR1 * n1 - FACTOR2, &rem);
	c1 = temp64;
	c2 = n1 * c1 + 1000000 * t1;

	/*
	 * now we only use single measure, every time we read
	 * the temperature, we will power on/down anadig thermal
	 * module
	 */
	writel(TEMPSENSE0_POWER_DOWN, &anatop->tempsense0_clr);
	writel(MISC0_REFTOP_SELBIASOFF, &anatop->ana_misc0_set);

	/* setup measure freq */
	reg = readl(&anatop->tempsense1);
	reg &= ~TEMPSENSE1_MEASURE_FREQ;
	reg |= MEASURE_FREQ;
	writel(reg, &anatop->tempsense1);

	/* start the measurement process */
	writel(TEMPSENSE0_MEASURE_TEMP, &anatop->tempsense0_clr);
	writel(TEMPSENSE0_FINISHED, &anatop->tempsense0_clr);
	writel(TEMPSENSE0_MEASURE_TEMP, &anatop->tempsense0_set);

	/* make sure that the latest temp is valid */
	while ((readl(&anatop->tempsense0) &
		TEMPSENSE0_FINISHED) == 0)
		udelay(10000);

	/* read temperature count */
	reg = readl(&anatop->tempsense0);
	n_meas = (reg & TEMPSENSE0_TEMP_CNT_MASK)
		>> TEMPSENSE0_TEMP_CNT_SHIFT;
	writel(TEMPSENSE0_FINISHED, &anatop->tempsense0_clr);

	/* Tmeas = (c2 - Nmeas * c1 + OFFSET) / 1000000 */
	temperature = div_s64_rem(c2 - n_meas * c1 + OFFSET, 1000000, &rem);

	/* power down anatop thermal sensor */
	writel(TEMPSENSE0_POWER_DOWN, &anatop->tempsense0_set);
	writel(MISC0_REFTOP_SELBIASOFF, &anatop->ana_misc0_clr);

	return temperature;
}
#elif defined(CONFIG_MX7)
static int read_cpu_temperature(struct udevice *dev)
{
	unsigned int reg, tmp;
	unsigned int raw_25c, te1;
	int temperature;
	unsigned int *priv = dev_get_priv(dev);
	u32 fuse = *priv;
	struct mxc_ccm_anatop_reg *ccm_anatop = (struct mxc_ccm_anatop_reg *)
						 ANATOP_BASE_ADDR;
	/*
	 * fuse data layout:
	 * [31:21] sensor value @ 25C
	 * [20:18] hot temperature value
	 * [17:9] sensor value of room
	 * [8:0] sensor value of hot
	 */

	raw_25c = fuse >> 21;
	if (raw_25c == 0)
		raw_25c = 25;

	te1 = (fuse >> 9) & 0x1ff;

	/*
	 * now we only use single measure, every time we read
	 * the temperature, we will power on/down anadig thermal
	 * module
	 */
	writel(TEMPMON_HW_ANADIG_TEMPSENSE1_POWER_DOWN_MASK, &ccm_anatop->tempsense1_clr);
	writel(PMU_REF_REFTOP_SELFBIASOFF_MASK, &ccm_anatop->ref_set);

	/* write measure freq */
	reg = readl(&ccm_anatop->tempsense1);
	reg &= ~TEMPMON_HW_ANADIG_TEMPSENSE1_MEASURE_FREQ_MASK;
	reg |= TEMPMON_HW_ANADIG_TEMPSENSE1_MEASURE_FREQ(MEASURE_FREQ);
	writel(reg, &ccm_anatop->tempsense1);

	writel(TEMPMON_HW_ANADIG_TEMPSENSE1_MEASURE_TEMP_MASK, &ccm_anatop->tempsense1_clr);
	writel(TEMPMON_HW_ANADIG_TEMPSENSE1_FINISHED_MASK, &ccm_anatop->tempsense1_clr);
	writel(TEMPMON_HW_ANADIG_TEMPSENSE1_MEASURE_TEMP_MASK, &ccm_anatop->tempsense1_set);

	if (soc_rev() >= CHIP_REV_1_1) {
		while ((readl(&ccm_anatop->tempsense1) &
		       TEMPMON_HW_ANADIG_TEMPSENSE1_FINISHED_MASK) == 0)
			;
		reg = readl(&ccm_anatop->tempsense1);
		tmp = (reg & TEMPMON_HW_ANADIG_TEMPSENSE1_TEMP_VALUE_MASK)
		       >> TEMPMON_HW_ANADIG_TEMPSENSE1_TEMP_VALUE_SHIFT;
	} else {
		/*
		 * Since we can not rely on finish bit, use 10ms
		 * delay to get temperature. From RM, 17us is
		 * enough to get data, but to gurantee to get
		 * the data, delay 10ms here.
		 */
		udelay(10000);
		reg = readl(&ccm_anatop->tempsense1);
		tmp = (reg & TEMPMON_HW_ANADIG_TEMPSENSE1_TEMP_VALUE_MASK)
		       >> TEMPMON_HW_ANADIG_TEMPSENSE1_TEMP_VALUE_SHIFT;
	}

	writel(TEMPMON_HW_ANADIG_TEMPSENSE1_FINISHED_MASK, &ccm_anatop->tempsense1_clr);

	/* power down anatop thermal sensor */
	writel(TEMPMON_HW_ANADIG_TEMPSENSE1_POWER_DOWN_MASK, &ccm_anatop->tempsense1_set);
	writel(PMU_REF_REFTOP_SELFBIASOFF_MASK, &ccm_anatop->ref_clr);

	/* Single point */
	temperature = tmp - (te1 - raw_25c);

	return temperature;
}
#endif

int imx_thermal_get_temp(struct udevice *dev, int *temp)
{
	struct thermal_data *priv = dev_get_priv(dev);
	int cpu_tmp = 0;

	cpu_tmp = read_cpu_temperature(dev);

	while (cpu_tmp >= priv->critical) {
		printf("CPU Temperature (%dC) too close to max (%dC)",
		       cpu_tmp, priv->maxc);
		puts(" waiting...\n");
		udelay(5000000);
		cpu_tmp = read_cpu_temperature(dev);
	}

	*temp = cpu_tmp;

	return 0;
}

static const struct dm_thermal_ops imx_thermal_ops = {
	.get_temp	= imx_thermal_get_temp,
};

static int imx_thermal_probe(struct udevice *dev)
{
	unsigned int fuse = ~0;

	const struct imx_thermal_plat *pdata = dev_get_platdata(dev);
	struct thermal_data *priv = dev_get_priv(dev);

	/* Read Temperature calibration data fuse */
	fuse_read(pdata->fuse_bank, pdata->fuse_word, &fuse);

	if (is_soc_type(MXC_SOC_MX6)) {
		/* Check for valid fuse */
		if (fuse == 0 || fuse == ~0) {
			debug("CPU:   Thermal invalid data, fuse: 0x%x\n",
				fuse);
			return -EPERM;
		}
	} else if (is_soc_type(MXC_SOC_MX7)) {
		/* No Calibration data in FUSE? */
		if ((fuse & 0x3ffff) == 0)
			return -EPERM;
		/* We do not support 105C TE2 */
		if (((fuse & 0x1c0000) >> 18) == 0x6)
			return -EPERM;
	}

	/* set critical cooling temp */
	get_cpu_temp_grade(&priv->minc, &priv->maxc);
	priv->critical = priv->maxc - TEMPERATURE_HOT_DELTA;
	priv->fuse = fuse;

	enable_thermal_clk();

	return 0;
}

U_BOOT_DRIVER(imx_thermal) = {
	.name	= "imx_thermal",
	.id	= UCLASS_THERMAL,
	.ops	= &imx_thermal_ops,
	.probe	= imx_thermal_probe,
	.priv_auto_alloc_size = sizeof(struct thermal_data),
	.flags  = DM_FLAG_PRE_RELOC,
};