xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision b009514f)
145f8b0ddSFabio Estevam // SPDX-License-Identifier: GPL-2.0
245f8b0ddSFabio Estevam //
345f8b0ddSFabio Estevam // Copyright 2013 Freescale Semiconductor, Inc.
4ca3de46bSShawn Guo 
5329fe7b1SAnson Huang #include <linux/clk.h>
6a1d00154SBastian Stender #include <linux/cpu.h>
74d753aa7SViresh Kumar #include <linux/cpufreq.h>
8ca3de46bSShawn Guo #include <linux/cpu_cooling.h>
9ca3de46bSShawn Guo #include <linux/delay.h>
10ca3de46bSShawn Guo #include <linux/device.h>
11ca3de46bSShawn Guo #include <linux/init.h>
1237713a1eSPhilipp Zabel #include <linux/interrupt.h>
13ca3de46bSShawn Guo #include <linux/io.h>
14ca3de46bSShawn Guo #include <linux/kernel.h>
15ca3de46bSShawn Guo #include <linux/mfd/syscon.h>
16ca3de46bSShawn Guo #include <linux/module.h>
17ca3de46bSShawn Guo #include <linux/of.h>
183c94f17eSAnson Huang #include <linux/of_device.h>
19ca3de46bSShawn Guo #include <linux/platform_device.h>
20ca3de46bSShawn Guo #include <linux/regmap.h>
21ca3de46bSShawn Guo #include <linux/slab.h>
22ca3de46bSShawn Guo #include <linux/thermal.h>
23ca3de46bSShawn Guo #include <linux/types.h>
24ae621557SLeonard Crestez #include <linux/nvmem-consumer.h>
25ca3de46bSShawn Guo 
26ca3de46bSShawn Guo #define REG_SET		0x4
27ca3de46bSShawn Guo #define REG_CLR		0x8
28ca3de46bSShawn Guo #define REG_TOG		0xc
29ca3de46bSShawn Guo 
30f085f672SAnson Huang /* i.MX6 specific */
31f085f672SAnson Huang #define IMX6_MISC0				0x0150
32f085f672SAnson Huang #define IMX6_MISC0_REFTOP_SELBIASOFF		(1 << 3)
33f085f672SAnson Huang #define IMX6_MISC1				0x0160
34f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPHIGH			(1 << 29)
353c94f17eSAnson Huang /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */
36f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPLOW			(1 << 28)
37f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPPANIC		(1 << 27)
38ca3de46bSShawn Guo 
39f085f672SAnson Huang #define IMX6_TEMPSENSE0				0x0180
40f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT	20
41f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_MASK	(0xfff << 20)
42f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT		8
43f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_MASK		(0xfff << 8)
44f085f672SAnson Huang #define IMX6_TEMPSENSE0_FINISHED		(1 << 2)
45f085f672SAnson Huang #define IMX6_TEMPSENSE0_MEASURE_TEMP		(1 << 1)
46f085f672SAnson Huang #define IMX6_TEMPSENSE0_POWER_DOWN		(1 << 0)
47ca3de46bSShawn Guo 
48f085f672SAnson Huang #define IMX6_TEMPSENSE1				0x0190
49f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ		0xffff
50f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT	0
51ca3de46bSShawn Guo 
52a2291badSTim Harvey #define OCOTP_MEM0			0x0480
53ca3de46bSShawn Guo #define OCOTP_ANA1			0x04e0
54ca3de46bSShawn Guo 
55f085f672SAnson Huang /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */
56f085f672SAnson Huang #define IMX6_TEMPSENSE2				0x0290
57f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT		0
58f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_MASK		0xfff
59f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT	16
60f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_MASK	0xfff0000
61f085f672SAnson Huang 
62f085f672SAnson Huang /* i.MX7 specific */
63f085f672SAnson Huang #define IMX7_ANADIG_DIGPROG			0x800
64f085f672SAnson Huang #define IMX7_TEMPSENSE0				0x300
65f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT	18
66f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_MASK	(0x1ff << 18)
67f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT	9
68f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_MASK		(0x1ff << 9)
69f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT		0
70f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_MASK		0x1ff
71f085f672SAnson Huang 
72f085f672SAnson Huang #define IMX7_TEMPSENSE1				0x310
73f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT	16
74f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK	(0xffff << 16)
75f085f672SAnson Huang #define IMX7_TEMPSENSE1_FINISHED		(1 << 11)
76f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_TEMP		(1 << 10)
77f085f672SAnson Huang #define IMX7_TEMPSENSE1_POWER_DOWN		(1 << 9)
78f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT	0
79f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_MASK		0x1ff
80f085f672SAnson Huang 
81ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */
82ca3de46bSShawn Guo enum imx_thermal_trip {
83ca3de46bSShawn Guo 	IMX_TRIP_PASSIVE,
84ca3de46bSShawn Guo 	IMX_TRIP_CRITICAL,
85ca3de46bSShawn Guo 	IMX_TRIP_NUM,
86ca3de46bSShawn Guo };
87ca3de46bSShawn Guo 
88ca3de46bSShawn Guo #define IMX_POLLING_DELAY		2000 /* millisecond */
89ca3de46bSShawn Guo #define IMX_PASSIVE_DELAY		1000
90ca3de46bSShawn Guo 
913c94f17eSAnson Huang #define TEMPMON_IMX6Q			1
923c94f17eSAnson Huang #define TEMPMON_IMX6SX			2
93f085f672SAnson Huang #define TEMPMON_IMX7D			3
943c94f17eSAnson Huang 
953c94f17eSAnson Huang struct thermal_soc_data {
963c94f17eSAnson Huang 	u32 version;
97f085f672SAnson Huang 
98f085f672SAnson Huang 	u32 sensor_ctrl;
99f085f672SAnson Huang 	u32 power_down_mask;
100f085f672SAnson Huang 	u32 measure_temp_mask;
101f085f672SAnson Huang 
102f085f672SAnson Huang 	u32 measure_freq_ctrl;
103f085f672SAnson Huang 	u32 measure_freq_mask;
104f085f672SAnson Huang 	u32 measure_freq_shift;
105f085f672SAnson Huang 
106f085f672SAnson Huang 	u32 temp_data;
107f085f672SAnson Huang 	u32 temp_value_mask;
108f085f672SAnson Huang 	u32 temp_value_shift;
109f085f672SAnson Huang 	u32 temp_valid_mask;
110f085f672SAnson Huang 
111f085f672SAnson Huang 	u32 panic_alarm_ctrl;
112f085f672SAnson Huang 	u32 panic_alarm_mask;
113f085f672SAnson Huang 	u32 panic_alarm_shift;
114f085f672SAnson Huang 
115f085f672SAnson Huang 	u32 high_alarm_ctrl;
116f085f672SAnson Huang 	u32 high_alarm_mask;
117f085f672SAnson Huang 	u32 high_alarm_shift;
118f085f672SAnson Huang 
119f085f672SAnson Huang 	u32 low_alarm_ctrl;
120f085f672SAnson Huang 	u32 low_alarm_mask;
121f085f672SAnson Huang 	u32 low_alarm_shift;
1223c94f17eSAnson Huang };
1233c94f17eSAnson Huang 
1243c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6q_data = {
1253c94f17eSAnson Huang 	.version = TEMPMON_IMX6Q,
126f085f672SAnson Huang 
127f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
128f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
129f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
130f085f672SAnson Huang 
131f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
132f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
133f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
134f085f672SAnson Huang 
135f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
136f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
137f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
138f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
139f085f672SAnson Huang 
140f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
141f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
142f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
1433c94f17eSAnson Huang };
1443c94f17eSAnson Huang 
1453c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6sx_data = {
1463c94f17eSAnson Huang 	.version = TEMPMON_IMX6SX,
147f085f672SAnson Huang 
148f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
149f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
150f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
151f085f672SAnson Huang 
152f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
153f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
154f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
155f085f672SAnson Huang 
156f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
157f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
158f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
159f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
160f085f672SAnson Huang 
161f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
162f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
163f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
164f085f672SAnson Huang 
165f085f672SAnson Huang 	.panic_alarm_ctrl = IMX6_TEMPSENSE2,
166f085f672SAnson Huang 	.panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK,
167f085f672SAnson Huang 	.panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT,
168f085f672SAnson Huang 
169f085f672SAnson Huang 	.low_alarm_ctrl = IMX6_TEMPSENSE2,
170f085f672SAnson Huang 	.low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK,
171f085f672SAnson Huang 	.low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT,
172f085f672SAnson Huang };
173f085f672SAnson Huang 
174f085f672SAnson Huang static struct thermal_soc_data thermal_imx7d_data = {
175f085f672SAnson Huang 	.version = TEMPMON_IMX7D,
176f085f672SAnson Huang 
177f085f672SAnson Huang 	.sensor_ctrl = IMX7_TEMPSENSE1,
178f085f672SAnson Huang 	.power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN,
179f085f672SAnson Huang 	.measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP,
180f085f672SAnson Huang 
181f085f672SAnson Huang 	.measure_freq_ctrl = IMX7_TEMPSENSE1,
182f085f672SAnson Huang 	.measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT,
183f085f672SAnson Huang 	.measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK,
184f085f672SAnson Huang 
185f085f672SAnson Huang 	.temp_data = IMX7_TEMPSENSE1,
186f085f672SAnson Huang 	.temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK,
187f085f672SAnson Huang 	.temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT,
188f085f672SAnson Huang 	.temp_valid_mask = IMX7_TEMPSENSE1_FINISHED,
189f085f672SAnson Huang 
190f085f672SAnson Huang 	.panic_alarm_ctrl = IMX7_TEMPSENSE1,
191f085f672SAnson Huang 	.panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK,
192f085f672SAnson Huang 	.panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT,
193f085f672SAnson Huang 
194f085f672SAnson Huang 	.high_alarm_ctrl = IMX7_TEMPSENSE0,
195f085f672SAnson Huang 	.high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK,
196f085f672SAnson Huang 	.high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT,
197f085f672SAnson Huang 
198f085f672SAnson Huang 	.low_alarm_ctrl = IMX7_TEMPSENSE0,
199f085f672SAnson Huang 	.low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK,
200f085f672SAnson Huang 	.low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT,
2013c94f17eSAnson Huang };
2023c94f17eSAnson Huang 
203ca3de46bSShawn Guo struct imx_thermal_data {
2044d753aa7SViresh Kumar 	struct cpufreq_policy *policy;
205ca3de46bSShawn Guo 	struct thermal_zone_device *tz;
206ca3de46bSShawn Guo 	struct thermal_cooling_device *cdev;
207ca3de46bSShawn Guo 	enum thermal_device_mode mode;
208ca3de46bSShawn Guo 	struct regmap *tempmon;
209ae621557SLeonard Crestez 	u32 c1, c2; /* See formula in imx_init_calib() */
21017e8351aSSascha Hauer 	int temp_passive;
21117e8351aSSascha Hauer 	int temp_critical;
212a2291badSTim Harvey 	int temp_max;
21317e8351aSSascha Hauer 	int alarm_temp;
21417e8351aSSascha Hauer 	int last_temp;
21537713a1eSPhilipp Zabel 	bool irq_enabled;
21637713a1eSPhilipp Zabel 	int irq;
217329fe7b1SAnson Huang 	struct clk *thermal_clk;
2183c94f17eSAnson Huang 	const struct thermal_soc_data *socdata;
219a2291badSTim Harvey 	const char *temp_grade;
220ca3de46bSShawn Guo };
221ca3de46bSShawn Guo 
2223c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data,
22317e8351aSSascha Hauer 			       int panic_temp)
2243c94f17eSAnson Huang {
225f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
2263c94f17eSAnson Huang 	struct regmap *map = data->tempmon;
2273c94f17eSAnson Huang 	int critical_value;
2283c94f17eSAnson Huang 
2293c94f17eSAnson Huang 	critical_value = (data->c2 - panic_temp) / data->c1;
230f085f672SAnson Huang 
231f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR,
232f085f672SAnson Huang 		     soc_data->panic_alarm_mask);
233f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET,
234f085f672SAnson Huang 		     critical_value << soc_data->panic_alarm_shift);
2353c94f17eSAnson Huang }
2363c94f17eSAnson Huang 
23737713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data,
23817e8351aSSascha Hauer 			       int alarm_temp)
23937713a1eSPhilipp Zabel {
24037713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
241f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
24237713a1eSPhilipp Zabel 	int alarm_value;
24337713a1eSPhilipp Zabel 
24437713a1eSPhilipp Zabel 	data->alarm_temp = alarm_temp;
245f085f672SAnson Huang 
246f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
247f085f672SAnson Huang 		alarm_value = alarm_temp / 1000 + data->c1 - 25;
248f085f672SAnson Huang 	else
249749e8be7SAnson Huang 		alarm_value = (data->c2 - alarm_temp) / data->c1;
250f085f672SAnson Huang 
251f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR,
252f085f672SAnson Huang 		     soc_data->high_alarm_mask);
253f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_SET,
254f085f672SAnson Huang 		     alarm_value << soc_data->high_alarm_shift);
25537713a1eSPhilipp Zabel }
25637713a1eSPhilipp Zabel 
25717e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
258ca3de46bSShawn Guo {
259ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
260f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
261ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
262ca3de46bSShawn Guo 	unsigned int n_meas;
26337713a1eSPhilipp Zabel 	bool wait;
264ca3de46bSShawn Guo 	u32 val;
265ca3de46bSShawn Guo 
26637713a1eSPhilipp Zabel 	if (data->mode == THERMAL_DEVICE_ENABLED) {
26737713a1eSPhilipp Zabel 		/* Check if a measurement is currently in progress */
268f085f672SAnson Huang 		regmap_read(map, soc_data->temp_data, &val);
269f085f672SAnson Huang 		wait = !(val & soc_data->temp_valid_mask);
27037713a1eSPhilipp Zabel 	} else {
271ca3de46bSShawn Guo 		/*
272ca3de46bSShawn Guo 		 * Every time we measure the temperature, we will power on the
273ca3de46bSShawn Guo 		 * temperature sensor, enable measurements, take a reading,
274ca3de46bSShawn Guo 		 * disable measurements, power off the temperature sensor.
275ca3de46bSShawn Guo 		 */
276f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
277f085f672SAnson Huang 			    soc_data->power_down_mask);
278f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
279f085f672SAnson Huang 			    soc_data->measure_temp_mask);
280ca3de46bSShawn Guo 
28137713a1eSPhilipp Zabel 		wait = true;
28237713a1eSPhilipp Zabel 	}
28337713a1eSPhilipp Zabel 
284ca3de46bSShawn Guo 	/*
285ca3de46bSShawn Guo 	 * According to the temp sensor designers, it may require up to ~17us
286ca3de46bSShawn Guo 	 * to complete a measurement.
287ca3de46bSShawn Guo 	 */
28837713a1eSPhilipp Zabel 	if (wait)
289ca3de46bSShawn Guo 		usleep_range(20, 50);
290ca3de46bSShawn Guo 
291f085f672SAnson Huang 	regmap_read(map, soc_data->temp_data, &val);
29237713a1eSPhilipp Zabel 
29337713a1eSPhilipp Zabel 	if (data->mode != THERMAL_DEVICE_ENABLED) {
294f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
295f085f672SAnson Huang 			     soc_data->measure_temp_mask);
296f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
297f085f672SAnson Huang 			     soc_data->power_down_mask);
29837713a1eSPhilipp Zabel 	}
299ca3de46bSShawn Guo 
300f085f672SAnson Huang 	if ((val & soc_data->temp_valid_mask) == 0) {
301ca3de46bSShawn Guo 		dev_dbg(&tz->device, "temp measurement never finished\n");
302ca3de46bSShawn Guo 		return -EAGAIN;
303ca3de46bSShawn Guo 	}
304ca3de46bSShawn Guo 
305f085f672SAnson Huang 	n_meas = (val & soc_data->temp_value_mask)
306f085f672SAnson Huang 		>> soc_data->temp_value_shift;
307ca3de46bSShawn Guo 
308ae621557SLeonard Crestez 	/* See imx_init_calib() for formula derivation */
309f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
310f085f672SAnson Huang 		*temp = (n_meas - data->c1 + 25) * 1000;
311f085f672SAnson Huang 	else
312749e8be7SAnson Huang 		*temp = data->c2 - n_meas * data->c1;
313ca3de46bSShawn Guo 
3143c94f17eSAnson Huang 	/* Update alarm value to next higher trip point for TEMPMON_IMX6Q */
3153c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6Q) {
3163c94f17eSAnson Huang 		if (data->alarm_temp == data->temp_passive &&
3173c94f17eSAnson Huang 			*temp >= data->temp_passive)
31837713a1eSPhilipp Zabel 			imx_set_alarm_temp(data, data->temp_critical);
3193c94f17eSAnson Huang 		if (data->alarm_temp == data->temp_critical &&
3203c94f17eSAnson Huang 			*temp < data->temp_passive) {
32137713a1eSPhilipp Zabel 			imx_set_alarm_temp(data, data->temp_passive);
32217e8351aSSascha Hauer 			dev_dbg(&tz->device, "thermal alarm off: T < %d\n",
32337713a1eSPhilipp Zabel 				data->alarm_temp / 1000);
32437713a1eSPhilipp Zabel 		}
3253c94f17eSAnson Huang 	}
32637713a1eSPhilipp Zabel 
32737713a1eSPhilipp Zabel 	if (*temp != data->last_temp) {
32817e8351aSSascha Hauer 		dev_dbg(&tz->device, "millicelsius: %d\n", *temp);
32937713a1eSPhilipp Zabel 		data->last_temp = *temp;
33037713a1eSPhilipp Zabel 	}
33137713a1eSPhilipp Zabel 
33237713a1eSPhilipp Zabel 	/* Reenable alarm IRQ if temperature below alarm temperature */
33337713a1eSPhilipp Zabel 	if (!data->irq_enabled && *temp < data->alarm_temp) {
33437713a1eSPhilipp Zabel 		data->irq_enabled = true;
33537713a1eSPhilipp Zabel 		enable_irq(data->irq);
336ca3de46bSShawn Guo 	}
337ca3de46bSShawn Guo 
338ca3de46bSShawn Guo 	return 0;
339ca3de46bSShawn Guo }
340ca3de46bSShawn Guo 
341ca3de46bSShawn Guo static int imx_get_mode(struct thermal_zone_device *tz,
342ca3de46bSShawn Guo 			enum thermal_device_mode *mode)
343ca3de46bSShawn Guo {
344ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
345ca3de46bSShawn Guo 
346ca3de46bSShawn Guo 	*mode = data->mode;
347ca3de46bSShawn Guo 
348ca3de46bSShawn Guo 	return 0;
349ca3de46bSShawn Guo }
350ca3de46bSShawn Guo 
351ca3de46bSShawn Guo static int imx_set_mode(struct thermal_zone_device *tz,
352ca3de46bSShawn Guo 			enum thermal_device_mode mode)
353ca3de46bSShawn Guo {
354ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
35537713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
356f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
357ca3de46bSShawn Guo 
358ca3de46bSShawn Guo 	if (mode == THERMAL_DEVICE_ENABLED) {
359ca3de46bSShawn Guo 		tz->polling_delay = IMX_POLLING_DELAY;
360ca3de46bSShawn Guo 		tz->passive_delay = IMX_PASSIVE_DELAY;
36137713a1eSPhilipp Zabel 
362f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
363f085f672SAnson Huang 			     soc_data->power_down_mask);
364f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
365f085f672SAnson Huang 			     soc_data->measure_temp_mask);
36637713a1eSPhilipp Zabel 
36737713a1eSPhilipp Zabel 		if (!data->irq_enabled) {
36837713a1eSPhilipp Zabel 			data->irq_enabled = true;
36937713a1eSPhilipp Zabel 			enable_irq(data->irq);
37037713a1eSPhilipp Zabel 		}
371ca3de46bSShawn Guo 	} else {
372f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
373f085f672SAnson Huang 			     soc_data->measure_temp_mask);
374f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
375f085f672SAnson Huang 			     soc_data->power_down_mask);
37637713a1eSPhilipp Zabel 
377ca3de46bSShawn Guo 		tz->polling_delay = 0;
378ca3de46bSShawn Guo 		tz->passive_delay = 0;
37937713a1eSPhilipp Zabel 
38037713a1eSPhilipp Zabel 		if (data->irq_enabled) {
38137713a1eSPhilipp Zabel 			disable_irq(data->irq);
38237713a1eSPhilipp Zabel 			data->irq_enabled = false;
38337713a1eSPhilipp Zabel 		}
384ca3de46bSShawn Guo 	}
385ca3de46bSShawn Guo 
386ca3de46bSShawn Guo 	data->mode = mode;
3870e70f466SSrinivas Pandruvada 	thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
388ca3de46bSShawn Guo 
389ca3de46bSShawn Guo 	return 0;
390ca3de46bSShawn Guo }
391ca3de46bSShawn Guo 
392ca3de46bSShawn Guo static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
393ca3de46bSShawn Guo 			     enum thermal_trip_type *type)
394ca3de46bSShawn Guo {
395ca3de46bSShawn Guo 	*type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
396ca3de46bSShawn Guo 					     THERMAL_TRIP_CRITICAL;
397ca3de46bSShawn Guo 	return 0;
398ca3de46bSShawn Guo }
399ca3de46bSShawn Guo 
40017e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
401ca3de46bSShawn Guo {
402017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
403017e5142SPhilipp Zabel 
404017e5142SPhilipp Zabel 	*temp = data->temp_critical;
405ca3de46bSShawn Guo 	return 0;
406ca3de46bSShawn Guo }
407ca3de46bSShawn Guo 
408ca3de46bSShawn Guo static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
40917e8351aSSascha Hauer 			     int *temp)
410ca3de46bSShawn Guo {
411017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
412017e5142SPhilipp Zabel 
413017e5142SPhilipp Zabel 	*temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive :
414017e5142SPhilipp Zabel 					     data->temp_critical;
415017e5142SPhilipp Zabel 	return 0;
416017e5142SPhilipp Zabel }
417017e5142SPhilipp Zabel 
418017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
41917e8351aSSascha Hauer 			     int temp)
420017e5142SPhilipp Zabel {
421017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
422017e5142SPhilipp Zabel 
423a2291badSTim Harvey 	/* do not allow changing critical threshold */
424017e5142SPhilipp Zabel 	if (trip == IMX_TRIP_CRITICAL)
425017e5142SPhilipp Zabel 		return -EPERM;
426017e5142SPhilipp Zabel 
427a2291badSTim Harvey 	/* do not allow passive to be set higher than critical */
428a2291badSTim Harvey 	if (temp < 0 || temp > data->temp_critical)
429017e5142SPhilipp Zabel 		return -EINVAL;
430017e5142SPhilipp Zabel 
431017e5142SPhilipp Zabel 	data->temp_passive = temp;
432017e5142SPhilipp Zabel 
43337713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
43437713a1eSPhilipp Zabel 
435ca3de46bSShawn Guo 	return 0;
436ca3de46bSShawn Guo }
437ca3de46bSShawn Guo 
438ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
439ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
440ca3de46bSShawn Guo {
441ca3de46bSShawn Guo 	int ret;
442ca3de46bSShawn Guo 
443ca3de46bSShawn Guo 	ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
444ca3de46bSShawn Guo 					       THERMAL_NO_LIMIT,
4456cd9e9f6SKapileshwar Singh 					       THERMAL_NO_LIMIT,
4466cd9e9f6SKapileshwar Singh 					       THERMAL_WEIGHT_DEFAULT);
447ca3de46bSShawn Guo 	if (ret) {
448ca3de46bSShawn Guo 		dev_err(&tz->device,
449ca3de46bSShawn Guo 			"binding zone %s with cdev %s failed:%d\n",
450ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
451ca3de46bSShawn Guo 		return ret;
452ca3de46bSShawn Guo 	}
453ca3de46bSShawn Guo 
454ca3de46bSShawn Guo 	return 0;
455ca3de46bSShawn Guo }
456ca3de46bSShawn Guo 
457ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
458ca3de46bSShawn Guo 		      struct thermal_cooling_device *cdev)
459ca3de46bSShawn Guo {
460ca3de46bSShawn Guo 	int ret;
461ca3de46bSShawn Guo 
462ca3de46bSShawn Guo 	ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
463ca3de46bSShawn Guo 	if (ret) {
464ca3de46bSShawn Guo 		dev_err(&tz->device,
465ca3de46bSShawn Guo 			"unbinding zone %s with cdev %s failed:%d\n",
466ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
467ca3de46bSShawn Guo 		return ret;
468ca3de46bSShawn Guo 	}
469ca3de46bSShawn Guo 
470ca3de46bSShawn Guo 	return 0;
471ca3de46bSShawn Guo }
472ca3de46bSShawn Guo 
473cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
474ca3de46bSShawn Guo 	.bind = imx_bind,
475ca3de46bSShawn Guo 	.unbind = imx_unbind,
476ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
477ca3de46bSShawn Guo 	.get_mode = imx_get_mode,
478ca3de46bSShawn Guo 	.set_mode = imx_set_mode,
479ca3de46bSShawn Guo 	.get_trip_type = imx_get_trip_type,
480ca3de46bSShawn Guo 	.get_trip_temp = imx_get_trip_temp,
481ca3de46bSShawn Guo 	.get_crit_temp = imx_get_crit_temp,
482017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
483ca3de46bSShawn Guo };
484ca3de46bSShawn Guo 
485e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
486ca3de46bSShawn Guo {
487ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
4884e5f61caSUwe Kleine-König 	int n1;
489749e8be7SAnson Huang 	u64 temp64;
490ca3de46bSShawn Guo 
491e4bb2240SUwe Kleine-König 	if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
492ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
493ca3de46bSShawn Guo 		return -EINVAL;
494ca3de46bSShawn Guo 	}
495ca3de46bSShawn Guo 
496ca3de46bSShawn Guo 	/*
497f085f672SAnson Huang 	 * On i.MX7D, we only use the calibration data at 25C to get the temp,
498f085f672SAnson Huang 	 * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
499f085f672SAnson Huang 	 */
500f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D) {
501f085f672SAnson Huang 		data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
502f085f672SAnson Huang 		return 0;
503f085f672SAnson Huang 	}
504f085f672SAnson Huang 
505f085f672SAnson Huang 	/*
506c5bbdb4bSUwe Kleine-König 	 * The sensor is calibrated at 25 °C (aka T1) and the value measured
507c5bbdb4bSUwe Kleine-König 	 * (aka N1) at this temperature is provided in bits [31:20] in the
508c5bbdb4bSUwe Kleine-König 	 * i.MX's OCOTP value ANA1.
509c5bbdb4bSUwe Kleine-König 	 * To find the actual temperature T, the following formula has to be used
510c5bbdb4bSUwe Kleine-König 	 * when reading value n from the sensor:
511c5bbdb4bSUwe Kleine-König 	 *
5124e5f61caSUwe Kleine-König 	 * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
5134e5f61caSUwe Kleine-König 	 *   = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
5144e5f61caSUwe Kleine-König 	 *   = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
515c5bbdb4bSUwe Kleine-König 	 *   = c2 - c1 * N
516c5bbdb4bSUwe Kleine-König 	 *
517c5bbdb4bSUwe Kleine-König 	 * with
518c5bbdb4bSUwe Kleine-König 	 *
5194e5f61caSUwe Kleine-König 	 *  T1' = 28.580661 °C
5204e5f61caSUwe Kleine-König 	 *   c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
5214e5f61caSUwe Kleine-König 	 *   c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
5224e5f61caSUwe Kleine-König 	 *      = T1' + N1 * c1
523ca3de46bSShawn Guo 	 */
524e4bb2240SUwe Kleine-König 	n1 = ocotp_ana1 >> 20;
525ca3de46bSShawn Guo 
5264e5f61caSUwe Kleine-König 	temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
527c5bbdb4bSUwe Kleine-König 	temp64 *= 1000; /* to get result in °mC */
5284e5f61caSUwe Kleine-König 	do_div(temp64, 15423 * n1 - 4148468);
529749e8be7SAnson Huang 	data->c1 = temp64;
5304e5f61caSUwe Kleine-König 	data->c2 = n1 * data->c1 + 28581;
531ca3de46bSShawn Guo 
532ae621557SLeonard Crestez 	return 0;
533a2291badSTim Harvey }
534a2291badSTim Harvey 
535e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
536ae621557SLeonard Crestez {
537ae621557SLeonard Crestez 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
538ae621557SLeonard Crestez 
539a2291badSTim Harvey 	/* The maximum die temp is specified by the Temperature Grade */
540e4bb2240SUwe Kleine-König 	switch ((ocotp_mem0 >> 6) & 0x3) {
541339d7492SUwe Kleine-König 	case 0: /* Commercial (0 to 95 °C) */
542a2291badSTim Harvey 		data->temp_grade = "Commercial";
543a2291badSTim Harvey 		data->temp_max = 95000;
544a2291badSTim Harvey 		break;
545339d7492SUwe Kleine-König 	case 1: /* Extended Commercial (-20 °C to 105 °C) */
546a2291badSTim Harvey 		data->temp_grade = "Extended Commercial";
547a2291badSTim Harvey 		data->temp_max = 105000;
548a2291badSTim Harvey 		break;
549339d7492SUwe Kleine-König 	case 2: /* Industrial (-40 °C to 105 °C) */
550a2291badSTim Harvey 		data->temp_grade = "Industrial";
551a2291badSTim Harvey 		data->temp_max = 105000;
552a2291badSTim Harvey 		break;
553339d7492SUwe Kleine-König 	case 3: /* Automotive (-40 °C to 125 °C) */
554a2291badSTim Harvey 		data->temp_grade = "Automotive";
555a2291badSTim Harvey 		data->temp_max = 125000;
556a2291badSTim Harvey 		break;
557a2291badSTim Harvey 	}
558017e5142SPhilipp Zabel 
559017e5142SPhilipp Zabel 	/*
560339d7492SUwe Kleine-König 	 * Set the critical trip point at 5 °C under max
561339d7492SUwe Kleine-König 	 * Set the passive trip point at 10 °C under max (changeable via sysfs)
562017e5142SPhilipp Zabel 	 */
563a2291badSTim Harvey 	data->temp_critical = data->temp_max - (1000 * 5);
564a2291badSTim Harvey 	data->temp_passive = data->temp_max - (1000 * 10);
565ae621557SLeonard Crestez }
566ae621557SLeonard Crestez 
567ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
568ae621557SLeonard Crestez {
569ae621557SLeonard Crestez 	struct regmap *map;
570ae621557SLeonard Crestez 	int ret;
571ae621557SLeonard Crestez 	u32 val;
572ae621557SLeonard Crestez 
573ae621557SLeonard Crestez 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
574ae621557SLeonard Crestez 					      "fsl,tempmon-data");
575ae621557SLeonard Crestez 	if (IS_ERR(map)) {
576ae621557SLeonard Crestez 		ret = PTR_ERR(map);
577ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
578ae621557SLeonard Crestez 		return ret;
579ae621557SLeonard Crestez 	}
580ae621557SLeonard Crestez 
581ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_ANA1, &val);
582ae621557SLeonard Crestez 	if (ret) {
583ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
584ae621557SLeonard Crestez 		return ret;
585ae621557SLeonard Crestez 	}
586ae621557SLeonard Crestez 	ret = imx_init_calib(pdev, val);
587ae621557SLeonard Crestez 	if (ret)
588ae621557SLeonard Crestez 		return ret;
589ae621557SLeonard Crestez 
590ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_MEM0, &val);
591ae621557SLeonard Crestez 	if (ret) {
592ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
593ae621557SLeonard Crestez 		return ret;
594ae621557SLeonard Crestez 	}
595ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
596ae621557SLeonard Crestez 
597ae621557SLeonard Crestez 	return 0;
598ae621557SLeonard Crestez }
599ae621557SLeonard Crestez 
600ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
601ae621557SLeonard Crestez {
602ae621557SLeonard Crestez 	int ret;
603ae621557SLeonard Crestez 	u32 val;
604ae621557SLeonard Crestez 
605ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
606ae621557SLeonard Crestez 	if (ret)
607ae621557SLeonard Crestez 		return ret;
608be926ceeSJean-Christophe Dubois 
609be926ceeSJean-Christophe Dubois 	ret = imx_init_calib(pdev, val);
610be926ceeSJean-Christophe Dubois 	if (ret)
611be926ceeSJean-Christophe Dubois 		return ret;
612ae621557SLeonard Crestez 
613ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
614ae621557SLeonard Crestez 	if (ret)
615ae621557SLeonard Crestez 		return ret;
616ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
617017e5142SPhilipp Zabel 
618ca3de46bSShawn Guo 	return 0;
619ca3de46bSShawn Guo }
620ca3de46bSShawn Guo 
62137713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
62237713a1eSPhilipp Zabel {
62337713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
62437713a1eSPhilipp Zabel 
62537713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
62637713a1eSPhilipp Zabel 	data->irq_enabled = false;
62737713a1eSPhilipp Zabel 
62837713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
62937713a1eSPhilipp Zabel }
63037713a1eSPhilipp Zabel 
63137713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
63237713a1eSPhilipp Zabel {
63337713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
63437713a1eSPhilipp Zabel 
63517e8351aSSascha Hauer 	dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n",
63637713a1eSPhilipp Zabel 		data->alarm_temp / 1000);
63737713a1eSPhilipp Zabel 
6380e70f466SSrinivas Pandruvada 	thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
63937713a1eSPhilipp Zabel 
64037713a1eSPhilipp Zabel 	return IRQ_HANDLED;
64137713a1eSPhilipp Zabel }
64237713a1eSPhilipp Zabel 
6433c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
6443c94f17eSAnson Huang 	{ .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
6453c94f17eSAnson Huang 	{ .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
646f085f672SAnson Huang 	{ .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
6473c94f17eSAnson Huang 	{ /* end */ }
6483c94f17eSAnson Huang };
6493c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
6503c94f17eSAnson Huang 
651c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ
652a1d00154SBastian Stender /*
653a1d00154SBastian Stender  * Create cooling device in case no #cooling-cells property is available in
654a1d00154SBastian Stender  * CPU node
655a1d00154SBastian Stender  */
656a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
657a1d00154SBastian Stender {
658c589c566SAnson Huang 	struct device_node *np;
659a1d00154SBastian Stender 	int ret;
660a1d00154SBastian Stender 
661c589c566SAnson Huang 	data->policy = cpufreq_cpu_get(0);
662c589c566SAnson Huang 	if (!data->policy) {
663c589c566SAnson Huang 		pr_debug("%s: CPUFreq policy not found\n", __func__);
664c589c566SAnson Huang 		return -EPROBE_DEFER;
665c589c566SAnson Huang 	}
666c589c566SAnson Huang 
667c589c566SAnson Huang 	np = of_get_cpu_node(data->policy->cpu, NULL);
668c589c566SAnson Huang 
669a1d00154SBastian Stender 	if (!np || !of_find_property(np, "#cooling-cells", NULL)) {
670a1d00154SBastian Stender 		data->cdev = cpufreq_cooling_register(data->policy);
671a1d00154SBastian Stender 		if (IS_ERR(data->cdev)) {
672a1d00154SBastian Stender 			ret = PTR_ERR(data->cdev);
673a1d00154SBastian Stender 			cpufreq_cpu_put(data->policy);
674a1d00154SBastian Stender 			return ret;
675a1d00154SBastian Stender 		}
676a1d00154SBastian Stender 	}
677a1d00154SBastian Stender 
678a1d00154SBastian Stender 	return 0;
679a1d00154SBastian Stender }
680a1d00154SBastian Stender 
681c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
682c589c566SAnson Huang {
683c589c566SAnson Huang 	cpufreq_cooling_unregister(data->cdev);
684c589c566SAnson Huang 	cpufreq_cpu_put(data->policy);
685c589c566SAnson Huang }
686c589c566SAnson Huang 
687c589c566SAnson Huang #else
688c589c566SAnson Huang 
689c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
690c589c566SAnson Huang {
691c589c566SAnson Huang 	return 0;
692c589c566SAnson Huang }
693c589c566SAnson Huang 
694c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
695c589c566SAnson Huang {
696c589c566SAnson Huang }
697c589c566SAnson Huang #endif
698c589c566SAnson Huang 
699ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
700ca3de46bSShawn Guo {
701ca3de46bSShawn Guo 	struct imx_thermal_data *data;
702ca3de46bSShawn Guo 	struct regmap *map;
70337713a1eSPhilipp Zabel 	int measure_freq;
704ca3de46bSShawn Guo 	int ret;
705ca3de46bSShawn Guo 
706ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
707ca3de46bSShawn Guo 	if (!data)
708ca3de46bSShawn Guo 		return -ENOMEM;
709ca3de46bSShawn Guo 
710ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
711ca3de46bSShawn Guo 	if (IS_ERR(map)) {
712ca3de46bSShawn Guo 		ret = PTR_ERR(map);
713ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
714ca3de46bSShawn Guo 		return ret;
715ca3de46bSShawn Guo 	}
716ca3de46bSShawn Guo 	data->tempmon = map;
717ca3de46bSShawn Guo 
718829bc78aSCorentin LABBE 	data->socdata = of_device_get_match_data(&pdev->dev);
7198b051ec3SShailendra Verma 	if (!data->socdata) {
7208b051ec3SShailendra Verma 		dev_err(&pdev->dev, "no device match found\n");
7218b051ec3SShailendra Verma 		return -ENODEV;
7228b051ec3SShailendra Verma 	}
7233c94f17eSAnson Huang 
7243c94f17eSAnson Huang 	/* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
7253c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX) {
726f085f672SAnson Huang 		regmap_write(map, IMX6_MISC1 + REG_CLR,
727f085f672SAnson Huang 			IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
728f085f672SAnson Huang 			| IMX6_MISC1_IRQ_TEMPPANIC);
7293c94f17eSAnson Huang 		/*
7303c94f17eSAnson Huang 		 * reset value of LOW ALARM is incorrect, set it to lowest
7313c94f17eSAnson Huang 		 * value to avoid false trigger of low alarm.
7323c94f17eSAnson Huang 		 */
733f085f672SAnson Huang 		regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
734f085f672SAnson Huang 			     data->socdata->low_alarm_mask);
7353c94f17eSAnson Huang 	}
7363c94f17eSAnson Huang 
73737713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
73837713a1eSPhilipp Zabel 	if (data->irq < 0)
73937713a1eSPhilipp Zabel 		return data->irq;
74037713a1eSPhilipp Zabel 
741ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
742ca3de46bSShawn Guo 
743ae621557SLeonard Crestez 	if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) {
744ae621557SLeonard Crestez 		ret = imx_init_from_nvmem_cells(pdev);
745925b3836SAnson Huang 		if (ret) {
746ae621557SLeonard Crestez 			if (ret == -EPROBE_DEFER)
747ca3de46bSShawn Guo 				return ret;
748925b3836SAnson Huang 
749ae621557SLeonard Crestez 			dev_err(&pdev->dev, "failed to init from nvmem: %d\n",
750ae621557SLeonard Crestez 				ret);
751ae621557SLeonard Crestez 			return ret;
752ae621557SLeonard Crestez 		}
753ae621557SLeonard Crestez 	} else {
754ae621557SLeonard Crestez 		ret = imx_init_from_tempmon_data(pdev);
755ae621557SLeonard Crestez 		if (ret) {
756337a4aecSAnson Huang 			dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
757ae621557SLeonard Crestez 			return ret;
758ae621557SLeonard Crestez 		}
759ca3de46bSShawn Guo 	}
760ca3de46bSShawn Guo 
761ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
762f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
763f085f672SAnson Huang 		     data->socdata->power_down_mask);
764f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
765f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
766f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
767f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
768f085f672SAnson Huang 	if (data->socdata->version != TEMPMON_IMX7D)
769f085f672SAnson Huang 		regmap_write(map, IMX6_MISC0 + REG_SET,
770f085f672SAnson Huang 			IMX6_MISC0_REFTOP_SELBIASOFF);
771f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
772f085f672SAnson Huang 		     data->socdata->power_down_mask);
773ca3de46bSShawn Guo 
774a1d00154SBastian Stender 	ret = imx_thermal_register_legacy_cooling(data);
775a1d00154SBastian Stender 	if (ret) {
776c589c566SAnson Huang 		if (ret == -EPROBE_DEFER)
777c589c566SAnson Huang 			return ret;
778c589c566SAnson Huang 
779ca3de46bSShawn Guo 		dev_err(&pdev->dev,
7804d753aa7SViresh Kumar 			"failed to register cpufreq cooling device: %d\n", ret);
781ca3de46bSShawn Guo 		return ret;
782ca3de46bSShawn Guo 	}
783ca3de46bSShawn Guo 
78490a21ff5SHeiner Kallweit 	data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
78590a21ff5SHeiner Kallweit 	if (IS_ERR(data->thermal_clk)) {
78690a21ff5SHeiner Kallweit 		ret = PTR_ERR(data->thermal_clk);
78790a21ff5SHeiner Kallweit 		if (ret != -EPROBE_DEFER)
78890a21ff5SHeiner Kallweit 			dev_err(&pdev->dev,
78990a21ff5SHeiner Kallweit 				"failed to get thermal clk: %d\n", ret);
790c589c566SAnson Huang 		goto legacy_cleanup;
79190a21ff5SHeiner Kallweit 	}
79290a21ff5SHeiner Kallweit 
79390a21ff5SHeiner Kallweit 	/*
79490a21ff5SHeiner Kallweit 	 * Thermal sensor needs clk on to get correct value, normally
79590a21ff5SHeiner Kallweit 	 * we should enable its clk before taking measurement and disable
79690a21ff5SHeiner Kallweit 	 * clk after measurement is done, but if alarm function is enabled,
79790a21ff5SHeiner Kallweit 	 * hardware will auto measure the temperature periodically, so we
79890a21ff5SHeiner Kallweit 	 * need to keep the clk always on for alarm function.
79990a21ff5SHeiner Kallweit 	 */
80090a21ff5SHeiner Kallweit 	ret = clk_prepare_enable(data->thermal_clk);
80190a21ff5SHeiner Kallweit 	if (ret) {
80290a21ff5SHeiner Kallweit 		dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
803c589c566SAnson Huang 		goto legacy_cleanup;
80490a21ff5SHeiner Kallweit 	}
80590a21ff5SHeiner Kallweit 
806ca3de46bSShawn Guo 	data->tz = thermal_zone_device_register("imx_thermal_zone",
807017e5142SPhilipp Zabel 						IMX_TRIP_NUM,
808017e5142SPhilipp Zabel 						BIT(IMX_TRIP_PASSIVE), data,
809ca3de46bSShawn Guo 						&imx_tz_ops, NULL,
810ca3de46bSShawn Guo 						IMX_PASSIVE_DELAY,
811ca3de46bSShawn Guo 						IMX_POLLING_DELAY);
812ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
813ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
814ca3de46bSShawn Guo 		dev_err(&pdev->dev,
815ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
816b6ad3981SAnson Huang 		goto clk_disable;
817ca3de46bSShawn Guo 	}
818ca3de46bSShawn Guo 
819a2291badSTim Harvey 	dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
820a2291badSTim Harvey 		 " critical:%dC passive:%dC\n", data->temp_grade,
821a2291badSTim Harvey 		 data->temp_max / 1000, data->temp_critical / 1000,
822a2291badSTim Harvey 		 data->temp_passive / 1000);
823a2291badSTim Harvey 
82437713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
825f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
826f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
82737713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
828f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
829f085f672SAnson Huang 		     measure_freq << data->socdata->measure_freq_shift);
83037713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, data->temp_passive);
8313c94f17eSAnson Huang 
8323c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX)
8333c94f17eSAnson Huang 		imx_set_panic_temp(data, data->temp_critical);
8343c94f17eSAnson Huang 
835f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
836f085f672SAnson Huang 		     data->socdata->power_down_mask);
837f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
838f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
83937713a1eSPhilipp Zabel 
840cf1ba1d7SMikhail Lappo 	data->irq_enabled = true;
841cf1ba1d7SMikhail Lappo 	data->mode = THERMAL_DEVICE_ENABLED;
842cf1ba1d7SMikhail Lappo 
84384866ee5SBai Ping 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
84484866ee5SBai Ping 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
84584866ee5SBai Ping 			0, "imx_thermal", data);
84684866ee5SBai Ping 	if (ret < 0) {
84784866ee5SBai Ping 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
848b6ad3981SAnson Huang 		goto thermal_zone_unregister;
84984866ee5SBai Ping 	}
85084866ee5SBai Ping 
851ca3de46bSShawn Guo 	return 0;
852b6ad3981SAnson Huang 
853b6ad3981SAnson Huang thermal_zone_unregister:
854b6ad3981SAnson Huang 	thermal_zone_device_unregister(data->tz);
855b6ad3981SAnson Huang clk_disable:
856b6ad3981SAnson Huang 	clk_disable_unprepare(data->thermal_clk);
857c589c566SAnson Huang legacy_cleanup:
858c589c566SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
859b6ad3981SAnson Huang 
860b6ad3981SAnson Huang 	return ret;
861ca3de46bSShawn Guo }
862ca3de46bSShawn Guo 
863ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
864ca3de46bSShawn Guo {
865ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
86637713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
86737713a1eSPhilipp Zabel 
86837713a1eSPhilipp Zabel 	/* Disable measurements */
869f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
870f085f672SAnson Huang 		     data->socdata->power_down_mask);
871329fe7b1SAnson Huang 	if (!IS_ERR(data->thermal_clk))
872329fe7b1SAnson Huang 		clk_disable_unprepare(data->thermal_clk);
873ca3de46bSShawn Guo 
874ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
875ca3de46bSShawn Guo 	cpufreq_cooling_unregister(data->cdev);
8764d753aa7SViresh Kumar 	cpufreq_cpu_put(data->policy);
877ca3de46bSShawn Guo 
878ca3de46bSShawn Guo 	return 0;
879ca3de46bSShawn Guo }
880ca3de46bSShawn Guo 
881b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev)
882ca3de46bSShawn Guo {
883ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
884ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
885ca3de46bSShawn Guo 
886ca3de46bSShawn Guo 	/*
887b46cce59SAnson Huang 	 * Need to disable thermal sensor, otherwise, when thermal core
888b46cce59SAnson Huang 	 * try to get temperature before thermal sensor resume, a wrong
889b46cce59SAnson Huang 	 * temperature will be read as the thermal sensor is powered
890b46cce59SAnson Huang 	 * down.
891ca3de46bSShawn Guo 	 */
892f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
893f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
894f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
895f085f672SAnson Huang 		     data->socdata->power_down_mask);
896b46cce59SAnson Huang 	data->mode = THERMAL_DEVICE_DISABLED;
897d26eef8bSAnson Huang 	clk_disable_unprepare(data->thermal_clk);
898ca3de46bSShawn Guo 
899ca3de46bSShawn Guo 	return 0;
900ca3de46bSShawn Guo }
901ca3de46bSShawn Guo 
902b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev)
903ca3de46bSShawn Guo {
904b46cce59SAnson Huang 	struct imx_thermal_data *data = dev_get_drvdata(dev);
905b46cce59SAnson Huang 	struct regmap *map = data->tempmon;
906e3bdc8d7SArvind Yadav 	int ret;
907b46cce59SAnson Huang 
908e3bdc8d7SArvind Yadav 	ret = clk_prepare_enable(data->thermal_clk);
909e3bdc8d7SArvind Yadav 	if (ret)
910e3bdc8d7SArvind Yadav 		return ret;
911b46cce59SAnson Huang 	/* Enabled thermal sensor after resume */
912f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
913f085f672SAnson Huang 		     data->socdata->power_down_mask);
914f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
915f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
916b46cce59SAnson Huang 	data->mode = THERMAL_DEVICE_ENABLED;
917b46cce59SAnson Huang 
918ca3de46bSShawn Guo 	return 0;
919ca3de46bSShawn Guo }
920ca3de46bSShawn Guo 
921ca3de46bSShawn Guo static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
922ca3de46bSShawn Guo 			 imx_thermal_suspend, imx_thermal_resume);
923ca3de46bSShawn Guo 
924ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
925ca3de46bSShawn Guo 	.driver = {
926ca3de46bSShawn Guo 		.name	= "imx_thermal",
927ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
928ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
929ca3de46bSShawn Guo 	},
930ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
931ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
932ca3de46bSShawn Guo };
933ca3de46bSShawn Guo module_platform_driver(imx_thermal);
934ca3de46bSShawn Guo 
935ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
936ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
937ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
938ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
939