xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision f085f672)
1ca3de46bSShawn Guo /*
2ca3de46bSShawn Guo  * Copyright 2013 Freescale Semiconductor, Inc.
3ca3de46bSShawn Guo  *
4ca3de46bSShawn Guo  * This program is free software; you can redistribute it and/or modify
5ca3de46bSShawn Guo  * it under the terms of the GNU General Public License version 2 as
6ca3de46bSShawn Guo  * published by the Free Software Foundation.
7ca3de46bSShawn Guo  *
8ca3de46bSShawn Guo  */
9ca3de46bSShawn Guo 
10329fe7b1SAnson Huang #include <linux/clk.h>
114d753aa7SViresh Kumar #include <linux/cpufreq.h>
12ca3de46bSShawn Guo #include <linux/cpu_cooling.h>
13ca3de46bSShawn Guo #include <linux/delay.h>
14ca3de46bSShawn Guo #include <linux/device.h>
15ca3de46bSShawn Guo #include <linux/init.h>
1637713a1eSPhilipp Zabel #include <linux/interrupt.h>
17ca3de46bSShawn Guo #include <linux/io.h>
18ca3de46bSShawn Guo #include <linux/kernel.h>
19ca3de46bSShawn Guo #include <linux/mfd/syscon.h>
20ca3de46bSShawn Guo #include <linux/module.h>
21ca3de46bSShawn Guo #include <linux/of.h>
223c94f17eSAnson Huang #include <linux/of_device.h>
23ca3de46bSShawn Guo #include <linux/platform_device.h>
24ca3de46bSShawn Guo #include <linux/regmap.h>
25ca3de46bSShawn Guo #include <linux/slab.h>
26ca3de46bSShawn Guo #include <linux/thermal.h>
27ca3de46bSShawn Guo #include <linux/types.h>
28ae621557SLeonard Crestez #include <linux/nvmem-consumer.h>
29ca3de46bSShawn Guo 
30ca3de46bSShawn Guo #define REG_SET		0x4
31ca3de46bSShawn Guo #define REG_CLR		0x8
32ca3de46bSShawn Guo #define REG_TOG		0xc
33ca3de46bSShawn Guo 
34f085f672SAnson Huang /* i.MX6 specific */
35f085f672SAnson Huang #define IMX6_MISC0				0x0150
36f085f672SAnson Huang #define IMX6_MISC0_REFTOP_SELBIASOFF		(1 << 3)
37f085f672SAnson Huang #define IMX6_MISC1				0x0160
38f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPHIGH			(1 << 29)
393c94f17eSAnson Huang /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */
40f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPLOW			(1 << 28)
41f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPPANIC		(1 << 27)
42ca3de46bSShawn Guo 
43f085f672SAnson Huang #define IMX6_TEMPSENSE0				0x0180
44f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT	20
45f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_MASK	(0xfff << 20)
46f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT		8
47f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_MASK		(0xfff << 8)
48f085f672SAnson Huang #define IMX6_TEMPSENSE0_FINISHED		(1 << 2)
49f085f672SAnson Huang #define IMX6_TEMPSENSE0_MEASURE_TEMP		(1 << 1)
50f085f672SAnson Huang #define IMX6_TEMPSENSE0_POWER_DOWN		(1 << 0)
51ca3de46bSShawn Guo 
52f085f672SAnson Huang #define IMX6_TEMPSENSE1				0x0190
53f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ		0xffff
54f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT	0
55ca3de46bSShawn Guo 
56a2291badSTim Harvey #define OCOTP_MEM0			0x0480
57ca3de46bSShawn Guo #define OCOTP_ANA1			0x04e0
58ca3de46bSShawn Guo 
59f085f672SAnson Huang /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */
60f085f672SAnson Huang #define IMX6_TEMPSENSE2				0x0290
61f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT		0
62f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_MASK		0xfff
63f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT	16
64f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_MASK	0xfff0000
65f085f672SAnson Huang 
66f085f672SAnson Huang /* i.MX7 specific */
67f085f672SAnson Huang #define IMX7_ANADIG_DIGPROG			0x800
68f085f672SAnson Huang #define IMX7_TEMPSENSE0				0x300
69f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT	18
70f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_MASK	(0x1ff << 18)
71f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT	9
72f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_MASK		(0x1ff << 9)
73f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT		0
74f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_MASK		0x1ff
75f085f672SAnson Huang 
76f085f672SAnson Huang #define IMX7_TEMPSENSE1				0x310
77f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT	16
78f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK	(0xffff << 16)
79f085f672SAnson Huang #define IMX7_TEMPSENSE1_FINISHED		(1 << 11)
80f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_TEMP		(1 << 10)
81f085f672SAnson Huang #define IMX7_TEMPSENSE1_POWER_DOWN		(1 << 9)
82f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT	0
83f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_MASK		0x1ff
84f085f672SAnson Huang 
85ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */
86ca3de46bSShawn Guo enum imx_thermal_trip {
87ca3de46bSShawn Guo 	IMX_TRIP_PASSIVE,
88ca3de46bSShawn Guo 	IMX_TRIP_CRITICAL,
89ca3de46bSShawn Guo 	IMX_TRIP_NUM,
90ca3de46bSShawn Guo };
91ca3de46bSShawn Guo 
92ca3de46bSShawn Guo #define IMX_POLLING_DELAY		2000 /* millisecond */
93ca3de46bSShawn Guo #define IMX_PASSIVE_DELAY		1000
94ca3de46bSShawn Guo 
953c94f17eSAnson Huang #define TEMPMON_IMX6Q			1
963c94f17eSAnson Huang #define TEMPMON_IMX6SX			2
97f085f672SAnson Huang #define TEMPMON_IMX7D			3
983c94f17eSAnson Huang 
993c94f17eSAnson Huang struct thermal_soc_data {
1003c94f17eSAnson Huang 	u32 version;
101f085f672SAnson Huang 
102f085f672SAnson Huang 	u32 sensor_ctrl;
103f085f672SAnson Huang 	u32 power_down_mask;
104f085f672SAnson Huang 	u32 measure_temp_mask;
105f085f672SAnson Huang 
106f085f672SAnson Huang 	u32 measure_freq_ctrl;
107f085f672SAnson Huang 	u32 measure_freq_mask;
108f085f672SAnson Huang 	u32 measure_freq_shift;
109f085f672SAnson Huang 
110f085f672SAnson Huang 	u32 temp_data;
111f085f672SAnson Huang 	u32 temp_value_mask;
112f085f672SAnson Huang 	u32 temp_value_shift;
113f085f672SAnson Huang 	u32 temp_valid_mask;
114f085f672SAnson Huang 
115f085f672SAnson Huang 	u32 panic_alarm_ctrl;
116f085f672SAnson Huang 	u32 panic_alarm_mask;
117f085f672SAnson Huang 	u32 panic_alarm_shift;
118f085f672SAnson Huang 
119f085f672SAnson Huang 	u32 high_alarm_ctrl;
120f085f672SAnson Huang 	u32 high_alarm_mask;
121f085f672SAnson Huang 	u32 high_alarm_shift;
122f085f672SAnson Huang 
123f085f672SAnson Huang 	u32 low_alarm_ctrl;
124f085f672SAnson Huang 	u32 low_alarm_mask;
125f085f672SAnson Huang 	u32 low_alarm_shift;
1263c94f17eSAnson Huang };
1273c94f17eSAnson Huang 
1283c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6q_data = {
1293c94f17eSAnson Huang 	.version = TEMPMON_IMX6Q,
130f085f672SAnson Huang 
131f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
132f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
133f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
134f085f672SAnson Huang 
135f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
136f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
137f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
138f085f672SAnson Huang 
139f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
140f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
141f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
142f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
143f085f672SAnson Huang 
144f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
145f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
146f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
1473c94f17eSAnson Huang };
1483c94f17eSAnson Huang 
1493c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6sx_data = {
1503c94f17eSAnson Huang 	.version = TEMPMON_IMX6SX,
151f085f672SAnson Huang 
152f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
153f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
154f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
155f085f672SAnson Huang 
156f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
157f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
158f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
159f085f672SAnson Huang 
160f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
161f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
162f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
163f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
164f085f672SAnson Huang 
165f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
166f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
167f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
168f085f672SAnson Huang 
169f085f672SAnson Huang 	.panic_alarm_ctrl = IMX6_TEMPSENSE2,
170f085f672SAnson Huang 	.panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK,
171f085f672SAnson Huang 	.panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT,
172f085f672SAnson Huang 
173f085f672SAnson Huang 	.low_alarm_ctrl = IMX6_TEMPSENSE2,
174f085f672SAnson Huang 	.low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK,
175f085f672SAnson Huang 	.low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT,
176f085f672SAnson Huang };
177f085f672SAnson Huang 
178f085f672SAnson Huang static struct thermal_soc_data thermal_imx7d_data = {
179f085f672SAnson Huang 	.version = TEMPMON_IMX7D,
180f085f672SAnson Huang 
181f085f672SAnson Huang 	.sensor_ctrl = IMX7_TEMPSENSE1,
182f085f672SAnson Huang 	.power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN,
183f085f672SAnson Huang 	.measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP,
184f085f672SAnson Huang 
185f085f672SAnson Huang 	.measure_freq_ctrl = IMX7_TEMPSENSE1,
186f085f672SAnson Huang 	.measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT,
187f085f672SAnson Huang 	.measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK,
188f085f672SAnson Huang 
189f085f672SAnson Huang 	.temp_data = IMX7_TEMPSENSE1,
190f085f672SAnson Huang 	.temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK,
191f085f672SAnson Huang 	.temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT,
192f085f672SAnson Huang 	.temp_valid_mask = IMX7_TEMPSENSE1_FINISHED,
193f085f672SAnson Huang 
194f085f672SAnson Huang 	.panic_alarm_ctrl = IMX7_TEMPSENSE1,
195f085f672SAnson Huang 	.panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK,
196f085f672SAnson Huang 	.panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT,
197f085f672SAnson Huang 
198f085f672SAnson Huang 	.high_alarm_ctrl = IMX7_TEMPSENSE0,
199f085f672SAnson Huang 	.high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK,
200f085f672SAnson Huang 	.high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT,
201f085f672SAnson Huang 
202f085f672SAnson Huang 	.low_alarm_ctrl = IMX7_TEMPSENSE0,
203f085f672SAnson Huang 	.low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK,
204f085f672SAnson Huang 	.low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT,
2053c94f17eSAnson Huang };
2063c94f17eSAnson Huang 
207ca3de46bSShawn Guo struct imx_thermal_data {
2084d753aa7SViresh Kumar 	struct cpufreq_policy *policy;
209ca3de46bSShawn Guo 	struct thermal_zone_device *tz;
210ca3de46bSShawn Guo 	struct thermal_cooling_device *cdev;
211ca3de46bSShawn Guo 	enum thermal_device_mode mode;
212ca3de46bSShawn Guo 	struct regmap *tempmon;
213ae621557SLeonard Crestez 	u32 c1, c2; /* See formula in imx_init_calib() */
21417e8351aSSascha Hauer 	int temp_passive;
21517e8351aSSascha Hauer 	int temp_critical;
216a2291badSTim Harvey 	int temp_max;
21717e8351aSSascha Hauer 	int alarm_temp;
21817e8351aSSascha Hauer 	int last_temp;
21937713a1eSPhilipp Zabel 	bool irq_enabled;
22037713a1eSPhilipp Zabel 	int irq;
221329fe7b1SAnson Huang 	struct clk *thermal_clk;
2223c94f17eSAnson Huang 	const struct thermal_soc_data *socdata;
223a2291badSTim Harvey 	const char *temp_grade;
224ca3de46bSShawn Guo };
225ca3de46bSShawn Guo 
2263c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data,
22717e8351aSSascha Hauer 			       int panic_temp)
2283c94f17eSAnson Huang {
229f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
2303c94f17eSAnson Huang 	struct regmap *map = data->tempmon;
2313c94f17eSAnson Huang 	int critical_value;
2323c94f17eSAnson Huang 
2333c94f17eSAnson Huang 	critical_value = (data->c2 - panic_temp) / data->c1;
234f085f672SAnson Huang 
235f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR,
236f085f672SAnson Huang 		     soc_data->panic_alarm_mask);
237f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET,
238f085f672SAnson Huang 		     critical_value << soc_data->panic_alarm_shift);
2393c94f17eSAnson Huang }
2403c94f17eSAnson Huang 
24137713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data,
24217e8351aSSascha Hauer 			       int alarm_temp)
24337713a1eSPhilipp Zabel {
24437713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
245f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
24637713a1eSPhilipp Zabel 	int alarm_value;
24737713a1eSPhilipp Zabel 
24837713a1eSPhilipp Zabel 	data->alarm_temp = alarm_temp;
249f085f672SAnson Huang 
250f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
251f085f672SAnson Huang 		alarm_value = alarm_temp / 1000 + data->c1 - 25;
252f085f672SAnson Huang 	else
253749e8be7SAnson Huang 		alarm_value = (data->c2 - alarm_temp) / data->c1;
254f085f672SAnson Huang 
255f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR,
256f085f672SAnson Huang 		     soc_data->high_alarm_mask);
257f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_SET,
258f085f672SAnson Huang 		     alarm_value << soc_data->high_alarm_shift);
25937713a1eSPhilipp Zabel }
26037713a1eSPhilipp Zabel 
26117e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
262ca3de46bSShawn Guo {
263ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
264f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
265ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
266ca3de46bSShawn Guo 	unsigned int n_meas;
26737713a1eSPhilipp Zabel 	bool wait;
268ca3de46bSShawn Guo 	u32 val;
269ca3de46bSShawn Guo 
27037713a1eSPhilipp Zabel 	if (data->mode == THERMAL_DEVICE_ENABLED) {
27137713a1eSPhilipp Zabel 		/* Check if a measurement is currently in progress */
272f085f672SAnson Huang 		regmap_read(map, soc_data->temp_data, &val);
273f085f672SAnson Huang 		wait = !(val & soc_data->temp_valid_mask);
27437713a1eSPhilipp Zabel 	} else {
275ca3de46bSShawn Guo 		/*
276ca3de46bSShawn Guo 		 * Every time we measure the temperature, we will power on the
277ca3de46bSShawn Guo 		 * temperature sensor, enable measurements, take a reading,
278ca3de46bSShawn Guo 		 * disable measurements, power off the temperature sensor.
279ca3de46bSShawn Guo 		 */
280f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
281f085f672SAnson Huang 			    soc_data->power_down_mask);
282f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
283f085f672SAnson Huang 			    soc_data->measure_temp_mask);
284ca3de46bSShawn Guo 
28537713a1eSPhilipp Zabel 		wait = true;
28637713a1eSPhilipp Zabel 	}
28737713a1eSPhilipp Zabel 
288ca3de46bSShawn Guo 	/*
289ca3de46bSShawn Guo 	 * According to the temp sensor designers, it may require up to ~17us
290ca3de46bSShawn Guo 	 * to complete a measurement.
291ca3de46bSShawn Guo 	 */
29237713a1eSPhilipp Zabel 	if (wait)
293ca3de46bSShawn Guo 		usleep_range(20, 50);
294ca3de46bSShawn Guo 
295f085f672SAnson Huang 	regmap_read(map, soc_data->temp_data, &val);
29637713a1eSPhilipp Zabel 
29737713a1eSPhilipp Zabel 	if (data->mode != THERMAL_DEVICE_ENABLED) {
298f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
299f085f672SAnson Huang 			     soc_data->measure_temp_mask);
300f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
301f085f672SAnson Huang 			     soc_data->power_down_mask);
30237713a1eSPhilipp Zabel 	}
303ca3de46bSShawn Guo 
304f085f672SAnson Huang 	if ((val & soc_data->temp_valid_mask) == 0) {
305ca3de46bSShawn Guo 		dev_dbg(&tz->device, "temp measurement never finished\n");
306ca3de46bSShawn Guo 		return -EAGAIN;
307ca3de46bSShawn Guo 	}
308ca3de46bSShawn Guo 
309f085f672SAnson Huang 	n_meas = (val & soc_data->temp_value_mask)
310f085f672SAnson Huang 		>> soc_data->temp_value_shift;
311ca3de46bSShawn Guo 
312ae621557SLeonard Crestez 	/* See imx_init_calib() for formula derivation */
313f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
314f085f672SAnson Huang 		*temp = (n_meas - data->c1 + 25) * 1000;
315f085f672SAnson Huang 	else
316749e8be7SAnson Huang 		*temp = data->c2 - n_meas * data->c1;
317ca3de46bSShawn Guo 
3183c94f17eSAnson Huang 	/* Update alarm value to next higher trip point for TEMPMON_IMX6Q */
3193c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6Q) {
3203c94f17eSAnson Huang 		if (data->alarm_temp == data->temp_passive &&
3213c94f17eSAnson Huang 			*temp >= data->temp_passive)
32237713a1eSPhilipp Zabel 			imx_set_alarm_temp(data, data->temp_critical);
3233c94f17eSAnson Huang 		if (data->alarm_temp == data->temp_critical &&
3243c94f17eSAnson Huang 			*temp < data->temp_passive) {
32537713a1eSPhilipp Zabel 			imx_set_alarm_temp(data, data->temp_passive);
32617e8351aSSascha Hauer 			dev_dbg(&tz->device, "thermal alarm off: T < %d\n",
32737713a1eSPhilipp Zabel 				data->alarm_temp / 1000);
32837713a1eSPhilipp Zabel 		}
3293c94f17eSAnson Huang 	}
33037713a1eSPhilipp Zabel 
33137713a1eSPhilipp Zabel 	if (*temp != data->last_temp) {
33217e8351aSSascha Hauer 		dev_dbg(&tz->device, "millicelsius: %d\n", *temp);
33337713a1eSPhilipp Zabel 		data->last_temp = *temp;
33437713a1eSPhilipp Zabel 	}
33537713a1eSPhilipp Zabel 
33637713a1eSPhilipp Zabel 	/* Reenable alarm IRQ if temperature below alarm temperature */
33737713a1eSPhilipp Zabel 	if (!data->irq_enabled && *temp < data->alarm_temp) {
33837713a1eSPhilipp Zabel 		data->irq_enabled = true;
33937713a1eSPhilipp Zabel 		enable_irq(data->irq);
340ca3de46bSShawn Guo 	}
341ca3de46bSShawn Guo 
342ca3de46bSShawn Guo 	return 0;
343ca3de46bSShawn Guo }
344ca3de46bSShawn Guo 
345ca3de46bSShawn Guo static int imx_get_mode(struct thermal_zone_device *tz,
346ca3de46bSShawn Guo 			enum thermal_device_mode *mode)
347ca3de46bSShawn Guo {
348ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
349ca3de46bSShawn Guo 
350ca3de46bSShawn Guo 	*mode = data->mode;
351ca3de46bSShawn Guo 
352ca3de46bSShawn Guo 	return 0;
353ca3de46bSShawn Guo }
354ca3de46bSShawn Guo 
355ca3de46bSShawn Guo static int imx_set_mode(struct thermal_zone_device *tz,
356ca3de46bSShawn Guo 			enum thermal_device_mode mode)
357ca3de46bSShawn Guo {
358ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
35937713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
360f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
361ca3de46bSShawn Guo 
362ca3de46bSShawn Guo 	if (mode == THERMAL_DEVICE_ENABLED) {
363ca3de46bSShawn Guo 		tz->polling_delay = IMX_POLLING_DELAY;
364ca3de46bSShawn Guo 		tz->passive_delay = IMX_PASSIVE_DELAY;
36537713a1eSPhilipp Zabel 
366f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
367f085f672SAnson Huang 			     soc_data->power_down_mask);
368f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
369f085f672SAnson Huang 			     soc_data->measure_temp_mask);
37037713a1eSPhilipp Zabel 
37137713a1eSPhilipp Zabel 		if (!data->irq_enabled) {
37237713a1eSPhilipp Zabel 			data->irq_enabled = true;
37337713a1eSPhilipp Zabel 			enable_irq(data->irq);
37437713a1eSPhilipp Zabel 		}
375ca3de46bSShawn Guo 	} else {
376f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
377f085f672SAnson Huang 			     soc_data->measure_temp_mask);
378f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
379f085f672SAnson Huang 			     soc_data->power_down_mask);
38037713a1eSPhilipp Zabel 
381ca3de46bSShawn Guo 		tz->polling_delay = 0;
382ca3de46bSShawn Guo 		tz->passive_delay = 0;
38337713a1eSPhilipp Zabel 
38437713a1eSPhilipp Zabel 		if (data->irq_enabled) {
38537713a1eSPhilipp Zabel 			disable_irq(data->irq);
38637713a1eSPhilipp Zabel 			data->irq_enabled = false;
38737713a1eSPhilipp Zabel 		}
388ca3de46bSShawn Guo 	}
389ca3de46bSShawn Guo 
390ca3de46bSShawn Guo 	data->mode = mode;
3910e70f466SSrinivas Pandruvada 	thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
392ca3de46bSShawn Guo 
393ca3de46bSShawn Guo 	return 0;
394ca3de46bSShawn Guo }
395ca3de46bSShawn Guo 
396ca3de46bSShawn Guo static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
397ca3de46bSShawn Guo 			     enum thermal_trip_type *type)
398ca3de46bSShawn Guo {
399ca3de46bSShawn Guo 	*type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
400ca3de46bSShawn Guo 					     THERMAL_TRIP_CRITICAL;
401ca3de46bSShawn Guo 	return 0;
402ca3de46bSShawn Guo }
403ca3de46bSShawn Guo 
40417e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
405ca3de46bSShawn Guo {
406017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
407017e5142SPhilipp Zabel 
408017e5142SPhilipp Zabel 	*temp = data->temp_critical;
409ca3de46bSShawn Guo 	return 0;
410ca3de46bSShawn Guo }
411ca3de46bSShawn Guo 
412ca3de46bSShawn Guo static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
41317e8351aSSascha Hauer 			     int *temp)
414ca3de46bSShawn Guo {
415017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
416017e5142SPhilipp Zabel 
417017e5142SPhilipp Zabel 	*temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive :
418017e5142SPhilipp Zabel 					     data->temp_critical;
419017e5142SPhilipp Zabel 	return 0;
420017e5142SPhilipp Zabel }
421017e5142SPhilipp Zabel 
422017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
42317e8351aSSascha Hauer 			     int temp)
424017e5142SPhilipp Zabel {
425017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
426017e5142SPhilipp Zabel 
427a2291badSTim Harvey 	/* do not allow changing critical threshold */
428017e5142SPhilipp Zabel 	if (trip == IMX_TRIP_CRITICAL)
429017e5142SPhilipp Zabel 		return -EPERM;
430017e5142SPhilipp Zabel 
431a2291badSTim Harvey 	/* do not allow passive to be set higher than critical */
432a2291badSTim Harvey 	if (temp < 0 || temp > data->temp_critical)
433017e5142SPhilipp Zabel 		return -EINVAL;
434017e5142SPhilipp Zabel 
435017e5142SPhilipp Zabel 	data->temp_passive = temp;
436017e5142SPhilipp Zabel 
43737713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
43837713a1eSPhilipp Zabel 
439ca3de46bSShawn Guo 	return 0;
440ca3de46bSShawn Guo }
441ca3de46bSShawn Guo 
442ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
443ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
444ca3de46bSShawn Guo {
445ca3de46bSShawn Guo 	int ret;
446ca3de46bSShawn Guo 
447ca3de46bSShawn Guo 	ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
448ca3de46bSShawn Guo 					       THERMAL_NO_LIMIT,
4496cd9e9f6SKapileshwar Singh 					       THERMAL_NO_LIMIT,
4506cd9e9f6SKapileshwar Singh 					       THERMAL_WEIGHT_DEFAULT);
451ca3de46bSShawn Guo 	if (ret) {
452ca3de46bSShawn Guo 		dev_err(&tz->device,
453ca3de46bSShawn Guo 			"binding zone %s with cdev %s failed:%d\n",
454ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
455ca3de46bSShawn Guo 		return ret;
456ca3de46bSShawn Guo 	}
457ca3de46bSShawn Guo 
458ca3de46bSShawn Guo 	return 0;
459ca3de46bSShawn Guo }
460ca3de46bSShawn Guo 
461ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
462ca3de46bSShawn Guo 		      struct thermal_cooling_device *cdev)
463ca3de46bSShawn Guo {
464ca3de46bSShawn Guo 	int ret;
465ca3de46bSShawn Guo 
466ca3de46bSShawn Guo 	ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
467ca3de46bSShawn Guo 	if (ret) {
468ca3de46bSShawn Guo 		dev_err(&tz->device,
469ca3de46bSShawn Guo 			"unbinding zone %s with cdev %s failed:%d\n",
470ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
471ca3de46bSShawn Guo 		return ret;
472ca3de46bSShawn Guo 	}
473ca3de46bSShawn Guo 
474ca3de46bSShawn Guo 	return 0;
475ca3de46bSShawn Guo }
476ca3de46bSShawn Guo 
477cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
478ca3de46bSShawn Guo 	.bind = imx_bind,
479ca3de46bSShawn Guo 	.unbind = imx_unbind,
480ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
481ca3de46bSShawn Guo 	.get_mode = imx_get_mode,
482ca3de46bSShawn Guo 	.set_mode = imx_set_mode,
483ca3de46bSShawn Guo 	.get_trip_type = imx_get_trip_type,
484ca3de46bSShawn Guo 	.get_trip_temp = imx_get_trip_temp,
485ca3de46bSShawn Guo 	.get_crit_temp = imx_get_crit_temp,
486017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
487ca3de46bSShawn Guo };
488ca3de46bSShawn Guo 
489e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
490ca3de46bSShawn Guo {
491ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
4924e5f61caSUwe Kleine-König 	int n1;
493749e8be7SAnson Huang 	u64 temp64;
494ca3de46bSShawn Guo 
495e4bb2240SUwe Kleine-König 	if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
496ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
497ca3de46bSShawn Guo 		return -EINVAL;
498ca3de46bSShawn Guo 	}
499ca3de46bSShawn Guo 
500ca3de46bSShawn Guo 	/*
501f085f672SAnson Huang 	 * On i.MX7D, we only use the calibration data at 25C to get the temp,
502f085f672SAnson Huang 	 * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
503f085f672SAnson Huang 	 */
504f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D) {
505f085f672SAnson Huang 		data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
506f085f672SAnson Huang 		return 0;
507f085f672SAnson Huang 	}
508f085f672SAnson Huang 
509f085f672SAnson Huang 	/*
510c5bbdb4bSUwe Kleine-König 	 * The sensor is calibrated at 25 °C (aka T1) and the value measured
511c5bbdb4bSUwe Kleine-König 	 * (aka N1) at this temperature is provided in bits [31:20] in the
512c5bbdb4bSUwe Kleine-König 	 * i.MX's OCOTP value ANA1.
513c5bbdb4bSUwe Kleine-König 	 * To find the actual temperature T, the following formula has to be used
514c5bbdb4bSUwe Kleine-König 	 * when reading value n from the sensor:
515c5bbdb4bSUwe Kleine-König 	 *
5164e5f61caSUwe Kleine-König 	 * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
5174e5f61caSUwe Kleine-König 	 *   = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
5184e5f61caSUwe Kleine-König 	 *   = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
519c5bbdb4bSUwe Kleine-König 	 *   = c2 - c1 * N
520c5bbdb4bSUwe Kleine-König 	 *
521c5bbdb4bSUwe Kleine-König 	 * with
522c5bbdb4bSUwe Kleine-König 	 *
5234e5f61caSUwe Kleine-König 	 *  T1' = 28.580661 °C
5244e5f61caSUwe Kleine-König 	 *   c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
5254e5f61caSUwe Kleine-König 	 *   c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
5264e5f61caSUwe Kleine-König 	 *      = T1' + N1 * c1
527ca3de46bSShawn Guo 	 */
528e4bb2240SUwe Kleine-König 	n1 = ocotp_ana1 >> 20;
529ca3de46bSShawn Guo 
5304e5f61caSUwe Kleine-König 	temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
531c5bbdb4bSUwe Kleine-König 	temp64 *= 1000; /* to get result in °mC */
5324e5f61caSUwe Kleine-König 	do_div(temp64, 15423 * n1 - 4148468);
533749e8be7SAnson Huang 	data->c1 = temp64;
5344e5f61caSUwe Kleine-König 	data->c2 = n1 * data->c1 + 28581;
535ca3de46bSShawn Guo 
536ae621557SLeonard Crestez 	return 0;
537a2291badSTim Harvey }
538a2291badSTim Harvey 
539e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
540ae621557SLeonard Crestez {
541ae621557SLeonard Crestez 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
542ae621557SLeonard Crestez 
543a2291badSTim Harvey 	/* The maximum die temp is specified by the Temperature Grade */
544e4bb2240SUwe Kleine-König 	switch ((ocotp_mem0 >> 6) & 0x3) {
545339d7492SUwe Kleine-König 	case 0: /* Commercial (0 to 95 °C) */
546a2291badSTim Harvey 		data->temp_grade = "Commercial";
547a2291badSTim Harvey 		data->temp_max = 95000;
548a2291badSTim Harvey 		break;
549339d7492SUwe Kleine-König 	case 1: /* Extended Commercial (-20 °C to 105 °C) */
550a2291badSTim Harvey 		data->temp_grade = "Extended Commercial";
551a2291badSTim Harvey 		data->temp_max = 105000;
552a2291badSTim Harvey 		break;
553339d7492SUwe Kleine-König 	case 2: /* Industrial (-40 °C to 105 °C) */
554a2291badSTim Harvey 		data->temp_grade = "Industrial";
555a2291badSTim Harvey 		data->temp_max = 105000;
556a2291badSTim Harvey 		break;
557339d7492SUwe Kleine-König 	case 3: /* Automotive (-40 °C to 125 °C) */
558a2291badSTim Harvey 		data->temp_grade = "Automotive";
559a2291badSTim Harvey 		data->temp_max = 125000;
560a2291badSTim Harvey 		break;
561a2291badSTim Harvey 	}
562017e5142SPhilipp Zabel 
563017e5142SPhilipp Zabel 	/*
564339d7492SUwe Kleine-König 	 * Set the critical trip point at 5 °C under max
565339d7492SUwe Kleine-König 	 * Set the passive trip point at 10 °C under max (changeable via sysfs)
566017e5142SPhilipp Zabel 	 */
567a2291badSTim Harvey 	data->temp_critical = data->temp_max - (1000 * 5);
568a2291badSTim Harvey 	data->temp_passive = data->temp_max - (1000 * 10);
569ae621557SLeonard Crestez }
570ae621557SLeonard Crestez 
571ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
572ae621557SLeonard Crestez {
573ae621557SLeonard Crestez 	struct regmap *map;
574ae621557SLeonard Crestez 	int ret;
575ae621557SLeonard Crestez 	u32 val;
576ae621557SLeonard Crestez 
577ae621557SLeonard Crestez 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
578ae621557SLeonard Crestez 					      "fsl,tempmon-data");
579ae621557SLeonard Crestez 	if (IS_ERR(map)) {
580ae621557SLeonard Crestez 		ret = PTR_ERR(map);
581ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
582ae621557SLeonard Crestez 		return ret;
583ae621557SLeonard Crestez 	}
584ae621557SLeonard Crestez 
585ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_ANA1, &val);
586ae621557SLeonard Crestez 	if (ret) {
587ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
588ae621557SLeonard Crestez 		return ret;
589ae621557SLeonard Crestez 	}
590ae621557SLeonard Crestez 	ret = imx_init_calib(pdev, val);
591ae621557SLeonard Crestez 	if (ret)
592ae621557SLeonard Crestez 		return ret;
593ae621557SLeonard Crestez 
594ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_MEM0, &val);
595ae621557SLeonard Crestez 	if (ret) {
596ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
597ae621557SLeonard Crestez 		return ret;
598ae621557SLeonard Crestez 	}
599ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
600ae621557SLeonard Crestez 
601ae621557SLeonard Crestez 	return 0;
602ae621557SLeonard Crestez }
603ae621557SLeonard Crestez 
604ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
605ae621557SLeonard Crestez {
606ae621557SLeonard Crestez 	int ret;
607ae621557SLeonard Crestez 	u32 val;
608ae621557SLeonard Crestez 
609ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
610ae621557SLeonard Crestez 	if (ret)
611ae621557SLeonard Crestez 		return ret;
612ae621557SLeonard Crestez 	imx_init_calib(pdev, val);
613ae621557SLeonard Crestez 
614ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
615ae621557SLeonard Crestez 	if (ret)
616ae621557SLeonard Crestez 		return ret;
617ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
618017e5142SPhilipp Zabel 
619ca3de46bSShawn Guo 	return 0;
620ca3de46bSShawn Guo }
621ca3de46bSShawn Guo 
62237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
62337713a1eSPhilipp Zabel {
62437713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
62537713a1eSPhilipp Zabel 
62637713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
62737713a1eSPhilipp Zabel 	data->irq_enabled = false;
62837713a1eSPhilipp Zabel 
62937713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
63037713a1eSPhilipp Zabel }
63137713a1eSPhilipp Zabel 
63237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
63337713a1eSPhilipp Zabel {
63437713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
63537713a1eSPhilipp Zabel 
63617e8351aSSascha Hauer 	dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n",
63737713a1eSPhilipp Zabel 		data->alarm_temp / 1000);
63837713a1eSPhilipp Zabel 
6390e70f466SSrinivas Pandruvada 	thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
64037713a1eSPhilipp Zabel 
64137713a1eSPhilipp Zabel 	return IRQ_HANDLED;
64237713a1eSPhilipp Zabel }
64337713a1eSPhilipp Zabel 
6443c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
6453c94f17eSAnson Huang 	{ .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
6463c94f17eSAnson Huang 	{ .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
647f085f672SAnson Huang 	{ .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
6483c94f17eSAnson Huang 	{ /* end */ }
6493c94f17eSAnson Huang };
6503c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
6513c94f17eSAnson Huang 
652ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
653ca3de46bSShawn Guo {
654ca3de46bSShawn Guo 	struct imx_thermal_data *data;
655ca3de46bSShawn Guo 	struct regmap *map;
65637713a1eSPhilipp Zabel 	int measure_freq;
657ca3de46bSShawn Guo 	int ret;
658ca3de46bSShawn Guo 
659ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
660ca3de46bSShawn Guo 	if (!data)
661ca3de46bSShawn Guo 		return -ENOMEM;
662ca3de46bSShawn Guo 
663ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
664ca3de46bSShawn Guo 	if (IS_ERR(map)) {
665ca3de46bSShawn Guo 		ret = PTR_ERR(map);
666ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
667ca3de46bSShawn Guo 		return ret;
668ca3de46bSShawn Guo 	}
669ca3de46bSShawn Guo 	data->tempmon = map;
670ca3de46bSShawn Guo 
671829bc78aSCorentin LABBE 	data->socdata = of_device_get_match_data(&pdev->dev);
6728b051ec3SShailendra Verma 	if (!data->socdata) {
6738b051ec3SShailendra Verma 		dev_err(&pdev->dev, "no device match found\n");
6748b051ec3SShailendra Verma 		return -ENODEV;
6758b051ec3SShailendra Verma 	}
6763c94f17eSAnson Huang 
6773c94f17eSAnson Huang 	/* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
6783c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX) {
679f085f672SAnson Huang 		regmap_write(map, IMX6_MISC1 + REG_CLR,
680f085f672SAnson Huang 			IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
681f085f672SAnson Huang 			| IMX6_MISC1_IRQ_TEMPPANIC);
6823c94f17eSAnson Huang 		/*
6833c94f17eSAnson Huang 		 * reset value of LOW ALARM is incorrect, set it to lowest
6843c94f17eSAnson Huang 		 * value to avoid false trigger of low alarm.
6853c94f17eSAnson Huang 		 */
686f085f672SAnson Huang 		regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
687f085f672SAnson Huang 			     data->socdata->low_alarm_mask);
6883c94f17eSAnson Huang 	}
6893c94f17eSAnson Huang 
69037713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
69137713a1eSPhilipp Zabel 	if (data->irq < 0)
69237713a1eSPhilipp Zabel 		return data->irq;
69337713a1eSPhilipp Zabel 
694ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
695ca3de46bSShawn Guo 
696ae621557SLeonard Crestez 	if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) {
697ae621557SLeonard Crestez 		ret = imx_init_from_nvmem_cells(pdev);
698ae621557SLeonard Crestez 		if (ret == -EPROBE_DEFER)
699ca3de46bSShawn Guo 			return ret;
700ae621557SLeonard Crestez 		if (ret) {
701ae621557SLeonard Crestez 			dev_err(&pdev->dev, "failed to init from nvmem: %d\n",
702ae621557SLeonard Crestez 				ret);
703ae621557SLeonard Crestez 			return ret;
704ae621557SLeonard Crestez 		}
705ae621557SLeonard Crestez 	} else {
706ae621557SLeonard Crestez 		ret = imx_init_from_tempmon_data(pdev);
707ae621557SLeonard Crestez 		if (ret) {
708ae621557SLeonard Crestez 			dev_err(&pdev->dev, "failed to init from from fsl,tempmon-data\n");
709ae621557SLeonard Crestez 			return ret;
710ae621557SLeonard Crestez 		}
711ca3de46bSShawn Guo 	}
712ca3de46bSShawn Guo 
713ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
714f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
715f085f672SAnson Huang 		     data->socdata->power_down_mask);
716f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
717f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
718f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
719f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
720f085f672SAnson Huang 	if (data->socdata->version != TEMPMON_IMX7D)
721f085f672SAnson Huang 		regmap_write(map, IMX6_MISC0 + REG_SET,
722f085f672SAnson Huang 			IMX6_MISC0_REFTOP_SELBIASOFF);
723f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
724f085f672SAnson Huang 		     data->socdata->power_down_mask);
725ca3de46bSShawn Guo 
7264d753aa7SViresh Kumar 	data->policy = cpufreq_cpu_get(0);
7274d753aa7SViresh Kumar 	if (!data->policy) {
7284d753aa7SViresh Kumar 		pr_debug("%s: CPUFreq policy not found\n", __func__);
7294d753aa7SViresh Kumar 		return -EPROBE_DEFER;
7304d753aa7SViresh Kumar 	}
7314d753aa7SViresh Kumar 
7324d753aa7SViresh Kumar 	data->cdev = cpufreq_cooling_register(data->policy);
733ca3de46bSShawn Guo 	if (IS_ERR(data->cdev)) {
734ca3de46bSShawn Guo 		ret = PTR_ERR(data->cdev);
735ca3de46bSShawn Guo 		dev_err(&pdev->dev,
7364d753aa7SViresh Kumar 			"failed to register cpufreq cooling device: %d\n", ret);
7374d753aa7SViresh Kumar 		cpufreq_cpu_put(data->policy);
738ca3de46bSShawn Guo 		return ret;
739ca3de46bSShawn Guo 	}
740ca3de46bSShawn Guo 
74190a21ff5SHeiner Kallweit 	data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
74290a21ff5SHeiner Kallweit 	if (IS_ERR(data->thermal_clk)) {
74390a21ff5SHeiner Kallweit 		ret = PTR_ERR(data->thermal_clk);
74490a21ff5SHeiner Kallweit 		if (ret != -EPROBE_DEFER)
74590a21ff5SHeiner Kallweit 			dev_err(&pdev->dev,
74690a21ff5SHeiner Kallweit 				"failed to get thermal clk: %d\n", ret);
74790a21ff5SHeiner Kallweit 		cpufreq_cooling_unregister(data->cdev);
7484d753aa7SViresh Kumar 		cpufreq_cpu_put(data->policy);
74990a21ff5SHeiner Kallweit 		return ret;
75090a21ff5SHeiner Kallweit 	}
75190a21ff5SHeiner Kallweit 
75290a21ff5SHeiner Kallweit 	/*
75390a21ff5SHeiner Kallweit 	 * Thermal sensor needs clk on to get correct value, normally
75490a21ff5SHeiner Kallweit 	 * we should enable its clk before taking measurement and disable
75590a21ff5SHeiner Kallweit 	 * clk after measurement is done, but if alarm function is enabled,
75690a21ff5SHeiner Kallweit 	 * hardware will auto measure the temperature periodically, so we
75790a21ff5SHeiner Kallweit 	 * need to keep the clk always on for alarm function.
75890a21ff5SHeiner Kallweit 	 */
75990a21ff5SHeiner Kallweit 	ret = clk_prepare_enable(data->thermal_clk);
76090a21ff5SHeiner Kallweit 	if (ret) {
76190a21ff5SHeiner Kallweit 		dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
76290a21ff5SHeiner Kallweit 		cpufreq_cooling_unregister(data->cdev);
7634d753aa7SViresh Kumar 		cpufreq_cpu_put(data->policy);
76490a21ff5SHeiner Kallweit 		return ret;
76590a21ff5SHeiner Kallweit 	}
76690a21ff5SHeiner Kallweit 
767ca3de46bSShawn Guo 	data->tz = thermal_zone_device_register("imx_thermal_zone",
768017e5142SPhilipp Zabel 						IMX_TRIP_NUM,
769017e5142SPhilipp Zabel 						BIT(IMX_TRIP_PASSIVE), data,
770ca3de46bSShawn Guo 						&imx_tz_ops, NULL,
771ca3de46bSShawn Guo 						IMX_PASSIVE_DELAY,
772ca3de46bSShawn Guo 						IMX_POLLING_DELAY);
773ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
774ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
775ca3de46bSShawn Guo 		dev_err(&pdev->dev,
776ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
77790a21ff5SHeiner Kallweit 		clk_disable_unprepare(data->thermal_clk);
778ca3de46bSShawn Guo 		cpufreq_cooling_unregister(data->cdev);
7794d753aa7SViresh Kumar 		cpufreq_cpu_put(data->policy);
780ca3de46bSShawn Guo 		return ret;
781ca3de46bSShawn Guo 	}
782ca3de46bSShawn Guo 
783a2291badSTim Harvey 	dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
784a2291badSTim Harvey 		 " critical:%dC passive:%dC\n", data->temp_grade,
785a2291badSTim Harvey 		 data->temp_max / 1000, data->temp_critical / 1000,
786a2291badSTim Harvey 		 data->temp_passive / 1000);
787a2291badSTim Harvey 
78837713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
789f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
790f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
79137713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
792f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
793f085f672SAnson Huang 		     measure_freq << data->socdata->measure_freq_shift);
79437713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, data->temp_passive);
7953c94f17eSAnson Huang 
7963c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX)
7973c94f17eSAnson Huang 		imx_set_panic_temp(data, data->temp_critical);
7983c94f17eSAnson Huang 
799f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
800f085f672SAnson Huang 		     data->socdata->power_down_mask);
801f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
802f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
80337713a1eSPhilipp Zabel 
804cf1ba1d7SMikhail Lappo 	data->irq_enabled = true;
805cf1ba1d7SMikhail Lappo 	data->mode = THERMAL_DEVICE_ENABLED;
806cf1ba1d7SMikhail Lappo 
80784866ee5SBai Ping 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
80884866ee5SBai Ping 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
80984866ee5SBai Ping 			0, "imx_thermal", data);
81084866ee5SBai Ping 	if (ret < 0) {
81184866ee5SBai Ping 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
81284866ee5SBai Ping 		clk_disable_unprepare(data->thermal_clk);
81384866ee5SBai Ping 		thermal_zone_device_unregister(data->tz);
81484866ee5SBai Ping 		cpufreq_cooling_unregister(data->cdev);
8154d753aa7SViresh Kumar 		cpufreq_cpu_put(data->policy);
81684866ee5SBai Ping 		return ret;
81784866ee5SBai Ping 	}
81884866ee5SBai Ping 
819ca3de46bSShawn Guo 	return 0;
820ca3de46bSShawn Guo }
821ca3de46bSShawn Guo 
822ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
823ca3de46bSShawn Guo {
824ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
82537713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
82637713a1eSPhilipp Zabel 
82737713a1eSPhilipp Zabel 	/* Disable measurements */
828f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
829f085f672SAnson Huang 		     data->socdata->power_down_mask);
830329fe7b1SAnson Huang 	if (!IS_ERR(data->thermal_clk))
831329fe7b1SAnson Huang 		clk_disable_unprepare(data->thermal_clk);
832ca3de46bSShawn Guo 
833ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
834ca3de46bSShawn Guo 	cpufreq_cooling_unregister(data->cdev);
8354d753aa7SViresh Kumar 	cpufreq_cpu_put(data->policy);
836ca3de46bSShawn Guo 
837ca3de46bSShawn Guo 	return 0;
838ca3de46bSShawn Guo }
839ca3de46bSShawn Guo 
840ca3de46bSShawn Guo #ifdef CONFIG_PM_SLEEP
841ca3de46bSShawn Guo static int imx_thermal_suspend(struct device *dev)
842ca3de46bSShawn Guo {
843ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
844ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
845ca3de46bSShawn Guo 
846ca3de46bSShawn Guo 	/*
847b46cce59SAnson Huang 	 * Need to disable thermal sensor, otherwise, when thermal core
848b46cce59SAnson Huang 	 * try to get temperature before thermal sensor resume, a wrong
849b46cce59SAnson Huang 	 * temperature will be read as the thermal sensor is powered
850b46cce59SAnson Huang 	 * down.
851ca3de46bSShawn Guo 	 */
852f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
853f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
854f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
855f085f672SAnson Huang 		     data->socdata->power_down_mask);
856b46cce59SAnson Huang 	data->mode = THERMAL_DEVICE_DISABLED;
857d26eef8bSAnson Huang 	clk_disable_unprepare(data->thermal_clk);
858ca3de46bSShawn Guo 
859ca3de46bSShawn Guo 	return 0;
860ca3de46bSShawn Guo }
861ca3de46bSShawn Guo 
862ca3de46bSShawn Guo static int imx_thermal_resume(struct device *dev)
863ca3de46bSShawn Guo {
864b46cce59SAnson Huang 	struct imx_thermal_data *data = dev_get_drvdata(dev);
865b46cce59SAnson Huang 	struct regmap *map = data->tempmon;
866e3bdc8d7SArvind Yadav 	int ret;
867b46cce59SAnson Huang 
868e3bdc8d7SArvind Yadav 	ret = clk_prepare_enable(data->thermal_clk);
869e3bdc8d7SArvind Yadav 	if (ret)
870e3bdc8d7SArvind Yadav 		return ret;
871b46cce59SAnson Huang 	/* Enabled thermal sensor after resume */
872f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
873f085f672SAnson Huang 		     data->socdata->power_down_mask);
874f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
875f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
876b46cce59SAnson Huang 	data->mode = THERMAL_DEVICE_ENABLED;
877b46cce59SAnson Huang 
878ca3de46bSShawn Guo 	return 0;
879ca3de46bSShawn Guo }
880ca3de46bSShawn Guo #endif
881ca3de46bSShawn Guo 
882ca3de46bSShawn Guo static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
883ca3de46bSShawn Guo 			 imx_thermal_suspend, imx_thermal_resume);
884ca3de46bSShawn Guo 
885ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
886ca3de46bSShawn Guo 	.driver = {
887ca3de46bSShawn Guo 		.name	= "imx_thermal",
888ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
889ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
890ca3de46bSShawn Guo 	},
891ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
892ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
893ca3de46bSShawn Guo };
894ca3de46bSShawn Guo module_platform_driver(imx_thermal);
895ca3de46bSShawn Guo 
896ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
897ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
898ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
899ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
900