xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision 5f68d078)
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>
64d753aa7SViresh Kumar #include <linux/cpufreq.h>
7ca3de46bSShawn Guo #include <linux/cpu_cooling.h>
8ca3de46bSShawn Guo #include <linux/delay.h>
937713a1eSPhilipp Zabel #include <linux/interrupt.h>
10ca3de46bSShawn Guo #include <linux/io.h>
11ca3de46bSShawn Guo #include <linux/mfd/syscon.h>
12ca3de46bSShawn Guo #include <linux/module.h>
13ca3de46bSShawn Guo #include <linux/of.h>
143c94f17eSAnson Huang #include <linux/of_device.h>
15ca3de46bSShawn Guo #include <linux/regmap.h>
16ca3de46bSShawn Guo #include <linux/thermal.h>
17ae621557SLeonard Crestez #include <linux/nvmem-consumer.h>
184cf2ddf1SOleksij Rempel #include <linux/pm_runtime.h>
19ca3de46bSShawn Guo 
20ca3de46bSShawn Guo #define REG_SET		0x4
21ca3de46bSShawn Guo #define REG_CLR		0x8
22ca3de46bSShawn Guo #define REG_TOG		0xc
23ca3de46bSShawn Guo 
24f085f672SAnson Huang /* i.MX6 specific */
25f085f672SAnson Huang #define IMX6_MISC0				0x0150
26f085f672SAnson Huang #define IMX6_MISC0_REFTOP_SELBIASOFF		(1 << 3)
27f085f672SAnson Huang #define IMX6_MISC1				0x0160
28f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPHIGH			(1 << 29)
293c94f17eSAnson Huang /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */
30f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPLOW			(1 << 28)
31f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPPANIC		(1 << 27)
32ca3de46bSShawn Guo 
33f085f672SAnson Huang #define IMX6_TEMPSENSE0				0x0180
34f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT	20
35f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_MASK	(0xfff << 20)
36f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT		8
37f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_MASK		(0xfff << 8)
38f085f672SAnson Huang #define IMX6_TEMPSENSE0_FINISHED		(1 << 2)
39f085f672SAnson Huang #define IMX6_TEMPSENSE0_MEASURE_TEMP		(1 << 1)
40f085f672SAnson Huang #define IMX6_TEMPSENSE0_POWER_DOWN		(1 << 0)
41ca3de46bSShawn Guo 
42f085f672SAnson Huang #define IMX6_TEMPSENSE1				0x0190
43f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ		0xffff
44f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT	0
45ca3de46bSShawn Guo 
46a2291badSTim Harvey #define OCOTP_MEM0			0x0480
47ca3de46bSShawn Guo #define OCOTP_ANA1			0x04e0
48ca3de46bSShawn Guo 
49f085f672SAnson Huang /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */
50f085f672SAnson Huang #define IMX6_TEMPSENSE2				0x0290
51f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT		0
52f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_MASK		0xfff
53f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT	16
54f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_MASK	0xfff0000
55f085f672SAnson Huang 
56f085f672SAnson Huang /* i.MX7 specific */
57f085f672SAnson Huang #define IMX7_ANADIG_DIGPROG			0x800
58f085f672SAnson Huang #define IMX7_TEMPSENSE0				0x300
59f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT	18
60f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_MASK	(0x1ff << 18)
61f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT	9
62f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_MASK		(0x1ff << 9)
63f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT		0
64f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_MASK		0x1ff
65f085f672SAnson Huang 
66f085f672SAnson Huang #define IMX7_TEMPSENSE1				0x310
67f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT	16
68f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK	(0xffff << 16)
69f085f672SAnson Huang #define IMX7_TEMPSENSE1_FINISHED		(1 << 11)
70f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_TEMP		(1 << 10)
71f085f672SAnson Huang #define IMX7_TEMPSENSE1_POWER_DOWN		(1 << 9)
72f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT	0
73f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_MASK		0x1ff
74f085f672SAnson Huang 
75ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */
76ca3de46bSShawn Guo enum imx_thermal_trip {
77ca3de46bSShawn Guo 	IMX_TRIP_PASSIVE,
78ca3de46bSShawn Guo 	IMX_TRIP_CRITICAL,
79ca3de46bSShawn Guo };
80ca3de46bSShawn Guo 
81ca3de46bSShawn Guo #define IMX_POLLING_DELAY		2000 /* millisecond */
82ca3de46bSShawn Guo #define IMX_PASSIVE_DELAY		1000
83ca3de46bSShawn Guo 
843c94f17eSAnson Huang #define TEMPMON_IMX6Q			1
853c94f17eSAnson Huang #define TEMPMON_IMX6SX			2
86f085f672SAnson Huang #define TEMPMON_IMX7D			3
873c94f17eSAnson Huang 
883c94f17eSAnson Huang struct thermal_soc_data {
893c94f17eSAnson Huang 	u32 version;
90f085f672SAnson Huang 
91f085f672SAnson Huang 	u32 sensor_ctrl;
92f085f672SAnson Huang 	u32 power_down_mask;
93f085f672SAnson Huang 	u32 measure_temp_mask;
94f085f672SAnson Huang 
95f085f672SAnson Huang 	u32 measure_freq_ctrl;
96f085f672SAnson Huang 	u32 measure_freq_mask;
97f085f672SAnson Huang 	u32 measure_freq_shift;
98f085f672SAnson Huang 
99f085f672SAnson Huang 	u32 temp_data;
100f085f672SAnson Huang 	u32 temp_value_mask;
101f085f672SAnson Huang 	u32 temp_value_shift;
102f085f672SAnson Huang 	u32 temp_valid_mask;
103f085f672SAnson Huang 
104f085f672SAnson Huang 	u32 panic_alarm_ctrl;
105f085f672SAnson Huang 	u32 panic_alarm_mask;
106f085f672SAnson Huang 	u32 panic_alarm_shift;
107f085f672SAnson Huang 
108f085f672SAnson Huang 	u32 high_alarm_ctrl;
109f085f672SAnson Huang 	u32 high_alarm_mask;
110f085f672SAnson Huang 	u32 high_alarm_shift;
111f085f672SAnson Huang 
112f085f672SAnson Huang 	u32 low_alarm_ctrl;
113f085f672SAnson Huang 	u32 low_alarm_mask;
114f085f672SAnson Huang 	u32 low_alarm_shift;
1153c94f17eSAnson Huang };
1163c94f17eSAnson Huang 
11730233a22SDaniel Lezcano static struct thermal_trip trips[] = {
11830233a22SDaniel Lezcano 	[IMX_TRIP_PASSIVE]  = { .type = THERMAL_TRIP_PASSIVE  },
11930233a22SDaniel Lezcano 	[IMX_TRIP_CRITICAL] = { .type = THERMAL_TRIP_CRITICAL },
12030233a22SDaniel Lezcano };
12130233a22SDaniel Lezcano 
1223c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6q_data = {
1233c94f17eSAnson Huang 	.version = TEMPMON_IMX6Q,
124f085f672SAnson Huang 
125f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
126f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
127f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
128f085f672SAnson Huang 
129f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
130f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
131f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
132f085f672SAnson Huang 
133f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
134f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
135f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
136f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
137f085f672SAnson Huang 
138f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
139f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
140f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
1413c94f17eSAnson Huang };
1423c94f17eSAnson Huang 
1433c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6sx_data = {
1443c94f17eSAnson Huang 	.version = TEMPMON_IMX6SX,
145f085f672SAnson Huang 
146f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
147f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
148f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
149f085f672SAnson Huang 
150f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
151f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
152f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
153f085f672SAnson Huang 
154f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
155f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
156f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
157f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
158f085f672SAnson Huang 
159f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
160f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
161f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
162f085f672SAnson Huang 
163f085f672SAnson Huang 	.panic_alarm_ctrl = IMX6_TEMPSENSE2,
164f085f672SAnson Huang 	.panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK,
165f085f672SAnson Huang 	.panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT,
166f085f672SAnson Huang 
167f085f672SAnson Huang 	.low_alarm_ctrl = IMX6_TEMPSENSE2,
168f085f672SAnson Huang 	.low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK,
169f085f672SAnson Huang 	.low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT,
170f085f672SAnson Huang };
171f085f672SAnson Huang 
172f085f672SAnson Huang static struct thermal_soc_data thermal_imx7d_data = {
173f085f672SAnson Huang 	.version = TEMPMON_IMX7D,
174f085f672SAnson Huang 
175f085f672SAnson Huang 	.sensor_ctrl = IMX7_TEMPSENSE1,
176f085f672SAnson Huang 	.power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN,
177f085f672SAnson Huang 	.measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP,
178f085f672SAnson Huang 
179f085f672SAnson Huang 	.measure_freq_ctrl = IMX7_TEMPSENSE1,
180f085f672SAnson Huang 	.measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT,
181f085f672SAnson Huang 	.measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK,
182f085f672SAnson Huang 
183f085f672SAnson Huang 	.temp_data = IMX7_TEMPSENSE1,
184f085f672SAnson Huang 	.temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK,
185f085f672SAnson Huang 	.temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT,
186f085f672SAnson Huang 	.temp_valid_mask = IMX7_TEMPSENSE1_FINISHED,
187f085f672SAnson Huang 
188f085f672SAnson Huang 	.panic_alarm_ctrl = IMX7_TEMPSENSE1,
189f085f672SAnson Huang 	.panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK,
190f085f672SAnson Huang 	.panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT,
191f085f672SAnson Huang 
192f085f672SAnson Huang 	.high_alarm_ctrl = IMX7_TEMPSENSE0,
193f085f672SAnson Huang 	.high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK,
194f085f672SAnson Huang 	.high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT,
195f085f672SAnson Huang 
196f085f672SAnson Huang 	.low_alarm_ctrl = IMX7_TEMPSENSE0,
197f085f672SAnson Huang 	.low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK,
198f085f672SAnson Huang 	.low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT,
1993c94f17eSAnson Huang };
2003c94f17eSAnson Huang 
201ca3de46bSShawn Guo struct imx_thermal_data {
2024cf2ddf1SOleksij Rempel 	struct device *dev;
2034d753aa7SViresh Kumar 	struct cpufreq_policy *policy;
204ca3de46bSShawn Guo 	struct thermal_zone_device *tz;
205ca3de46bSShawn Guo 	struct thermal_cooling_device *cdev;
206ca3de46bSShawn Guo 	struct regmap *tempmon;
207ae621557SLeonard Crestez 	u32 c1, c2; /* See formula in imx_init_calib() */
208a2291badSTim Harvey 	int temp_max;
20917e8351aSSascha Hauer 	int alarm_temp;
21017e8351aSSascha Hauer 	int last_temp;
21137713a1eSPhilipp Zabel 	bool irq_enabled;
21237713a1eSPhilipp Zabel 	int irq;
213329fe7b1SAnson Huang 	struct clk *thermal_clk;
2143c94f17eSAnson Huang 	const struct thermal_soc_data *socdata;
215a2291badSTim Harvey 	const char *temp_grade;
216ca3de46bSShawn Guo };
217ca3de46bSShawn Guo 
2183c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data,
21917e8351aSSascha Hauer 			       int panic_temp)
2203c94f17eSAnson Huang {
221f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
2223c94f17eSAnson Huang 	struct regmap *map = data->tempmon;
2233c94f17eSAnson Huang 	int critical_value;
2243c94f17eSAnson Huang 
2253c94f17eSAnson Huang 	critical_value = (data->c2 - panic_temp) / data->c1;
226f085f672SAnson Huang 
227f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR,
228f085f672SAnson Huang 		     soc_data->panic_alarm_mask);
229f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET,
230f085f672SAnson Huang 		     critical_value << soc_data->panic_alarm_shift);
2313c94f17eSAnson Huang }
2323c94f17eSAnson Huang 
23337713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data,
23417e8351aSSascha Hauer 			       int alarm_temp)
23537713a1eSPhilipp Zabel {
23637713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
237f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
23837713a1eSPhilipp Zabel 	int alarm_value;
23937713a1eSPhilipp Zabel 
24037713a1eSPhilipp Zabel 	data->alarm_temp = alarm_temp;
241f085f672SAnson Huang 
242f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
243f085f672SAnson Huang 		alarm_value = alarm_temp / 1000 + data->c1 - 25;
244f085f672SAnson Huang 	else
245749e8be7SAnson Huang 		alarm_value = (data->c2 - alarm_temp) / data->c1;
246f085f672SAnson Huang 
247f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR,
248f085f672SAnson Huang 		     soc_data->high_alarm_mask);
249f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_SET,
250f085f672SAnson Huang 		     alarm_value << soc_data->high_alarm_shift);
25137713a1eSPhilipp Zabel }
25237713a1eSPhilipp Zabel 
25317e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
254ca3de46bSShawn Guo {
255*5f68d078SDaniel Lezcano 	struct imx_thermal_data *data = thermal_zone_device_priv(tz);
256f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
257ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
258ca3de46bSShawn Guo 	unsigned int n_meas;
259ca3de46bSShawn Guo 	u32 val;
2604cf2ddf1SOleksij Rempel 	int ret;
261ca3de46bSShawn Guo 
2624cf2ddf1SOleksij Rempel 	ret = pm_runtime_resume_and_get(data->dev);
2634cf2ddf1SOleksij Rempel 	if (ret < 0)
2644cf2ddf1SOleksij Rempel 		return ret;
265ca3de46bSShawn Guo 
266f085f672SAnson Huang 	regmap_read(map, soc_data->temp_data, &val);
26737713a1eSPhilipp Zabel 
268f085f672SAnson Huang 	if ((val & soc_data->temp_valid_mask) == 0) {
269ca3de46bSShawn Guo 		dev_dbg(&tz->device, "temp measurement never finished\n");
270ca3de46bSShawn Guo 		return -EAGAIN;
271ca3de46bSShawn Guo 	}
272ca3de46bSShawn Guo 
273f085f672SAnson Huang 	n_meas = (val & soc_data->temp_value_mask)
274f085f672SAnson Huang 		>> soc_data->temp_value_shift;
275ca3de46bSShawn Guo 
276ae621557SLeonard Crestez 	/* See imx_init_calib() for formula derivation */
277f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
278f085f672SAnson Huang 		*temp = (n_meas - data->c1 + 25) * 1000;
279f085f672SAnson Huang 	else
280749e8be7SAnson Huang 		*temp = data->c2 - n_meas * data->c1;
281ca3de46bSShawn Guo 
2823c94f17eSAnson Huang 	/* Update alarm value to next higher trip point for TEMPMON_IMX6Q */
2833c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6Q) {
28430233a22SDaniel Lezcano 		if (data->alarm_temp == trips[IMX_TRIP_PASSIVE].temperature &&
28530233a22SDaniel Lezcano 			*temp >= trips[IMX_TRIP_PASSIVE].temperature)
28630233a22SDaniel Lezcano 			imx_set_alarm_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
28730233a22SDaniel Lezcano 		if (data->alarm_temp == trips[IMX_TRIP_CRITICAL].temperature &&
28830233a22SDaniel Lezcano 			*temp < trips[IMX_TRIP_PASSIVE].temperature) {
28930233a22SDaniel Lezcano 			imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
29017e8351aSSascha Hauer 			dev_dbg(&tz->device, "thermal alarm off: T < %d\n",
29137713a1eSPhilipp Zabel 				data->alarm_temp / 1000);
29237713a1eSPhilipp Zabel 		}
2933c94f17eSAnson Huang 	}
29437713a1eSPhilipp Zabel 
29537713a1eSPhilipp Zabel 	if (*temp != data->last_temp) {
29617e8351aSSascha Hauer 		dev_dbg(&tz->device, "millicelsius: %d\n", *temp);
29737713a1eSPhilipp Zabel 		data->last_temp = *temp;
29837713a1eSPhilipp Zabel 	}
29937713a1eSPhilipp Zabel 
30037713a1eSPhilipp Zabel 	/* Reenable alarm IRQ if temperature below alarm temperature */
30137713a1eSPhilipp Zabel 	if (!data->irq_enabled && *temp < data->alarm_temp) {
30237713a1eSPhilipp Zabel 		data->irq_enabled = true;
30337713a1eSPhilipp Zabel 		enable_irq(data->irq);
304ca3de46bSShawn Guo 	}
305ca3de46bSShawn Guo 
3064cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
3074cf2ddf1SOleksij Rempel 
308ca3de46bSShawn Guo 	return 0;
309ca3de46bSShawn Guo }
310ca3de46bSShawn Guo 
311f5e50bf4SAndrzej Pietrasiewicz static int imx_change_mode(struct thermal_zone_device *tz,
312ca3de46bSShawn Guo 			   enum thermal_device_mode mode)
313ca3de46bSShawn Guo {
314*5f68d078SDaniel Lezcano 	struct imx_thermal_data *data = thermal_zone_device_priv(tz);
315ca3de46bSShawn Guo 
316ca3de46bSShawn Guo 	if (mode == THERMAL_DEVICE_ENABLED) {
3174cf2ddf1SOleksij Rempel 		pm_runtime_get(data->dev);
31837713a1eSPhilipp Zabel 
31937713a1eSPhilipp Zabel 		if (!data->irq_enabled) {
32037713a1eSPhilipp Zabel 			data->irq_enabled = true;
32137713a1eSPhilipp Zabel 			enable_irq(data->irq);
32237713a1eSPhilipp Zabel 		}
323ca3de46bSShawn Guo 	} else {
3244cf2ddf1SOleksij Rempel 		pm_runtime_put(data->dev);
32537713a1eSPhilipp Zabel 
32637713a1eSPhilipp Zabel 		if (data->irq_enabled) {
32737713a1eSPhilipp Zabel 			disable_irq(data->irq);
32837713a1eSPhilipp Zabel 			data->irq_enabled = false;
32937713a1eSPhilipp Zabel 		}
330ca3de46bSShawn Guo 	}
331ca3de46bSShawn Guo 
332ca3de46bSShawn Guo 	return 0;
333ca3de46bSShawn Guo }
334ca3de46bSShawn Guo 
33517e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
336ca3de46bSShawn Guo {
33730233a22SDaniel Lezcano 	*temp = trips[IMX_TRIP_CRITICAL].temperature;
338017e5142SPhilipp Zabel 
339017e5142SPhilipp Zabel 	return 0;
340017e5142SPhilipp Zabel }
341017e5142SPhilipp Zabel 
342017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
34317e8351aSSascha Hauer 			     int temp)
344017e5142SPhilipp Zabel {
345*5f68d078SDaniel Lezcano 	struct imx_thermal_data *data = thermal_zone_device_priv(tz);
3464cf2ddf1SOleksij Rempel 	int ret;
3474cf2ddf1SOleksij Rempel 
3484cf2ddf1SOleksij Rempel 	ret = pm_runtime_resume_and_get(data->dev);
3494cf2ddf1SOleksij Rempel 	if (ret < 0)
3504cf2ddf1SOleksij Rempel 		return ret;
351017e5142SPhilipp Zabel 
352a2291badSTim Harvey 	/* do not allow changing critical threshold */
353017e5142SPhilipp Zabel 	if (trip == IMX_TRIP_CRITICAL)
354017e5142SPhilipp Zabel 		return -EPERM;
355017e5142SPhilipp Zabel 
356a2291badSTim Harvey 	/* do not allow passive to be set higher than critical */
35730233a22SDaniel Lezcano 	if (temp < 0 || temp > trips[IMX_TRIP_CRITICAL].temperature)
358017e5142SPhilipp Zabel 		return -EINVAL;
359017e5142SPhilipp Zabel 
36030233a22SDaniel Lezcano 	trips[IMX_TRIP_PASSIVE].temperature = temp;
361017e5142SPhilipp Zabel 
36237713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
36337713a1eSPhilipp Zabel 
3644cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
3654cf2ddf1SOleksij Rempel 
366ca3de46bSShawn Guo 	return 0;
367ca3de46bSShawn Guo }
368ca3de46bSShawn Guo 
369ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
370ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
371ca3de46bSShawn Guo {
372ca3de46bSShawn Guo 	int ret;
373ca3de46bSShawn Guo 
374ca3de46bSShawn Guo 	ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
375ca3de46bSShawn Guo 					       THERMAL_NO_LIMIT,
3766cd9e9f6SKapileshwar Singh 					       THERMAL_NO_LIMIT,
3776cd9e9f6SKapileshwar Singh 					       THERMAL_WEIGHT_DEFAULT);
378ca3de46bSShawn Guo 	if (ret) {
379ca3de46bSShawn Guo 		dev_err(&tz->device,
380ca3de46bSShawn Guo 			"binding zone %s with cdev %s failed:%d\n",
381ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
382ca3de46bSShawn Guo 		return ret;
383ca3de46bSShawn Guo 	}
384ca3de46bSShawn Guo 
385ca3de46bSShawn Guo 	return 0;
386ca3de46bSShawn Guo }
387ca3de46bSShawn Guo 
388ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
389ca3de46bSShawn Guo 		      struct thermal_cooling_device *cdev)
390ca3de46bSShawn Guo {
391ca3de46bSShawn Guo 	int ret;
392ca3de46bSShawn Guo 
393ca3de46bSShawn Guo 	ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
394ca3de46bSShawn Guo 	if (ret) {
395ca3de46bSShawn Guo 		dev_err(&tz->device,
396ca3de46bSShawn Guo 			"unbinding zone %s with cdev %s failed:%d\n",
397ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
398ca3de46bSShawn Guo 		return ret;
399ca3de46bSShawn Guo 	}
400ca3de46bSShawn Guo 
401ca3de46bSShawn Guo 	return 0;
402ca3de46bSShawn Guo }
403ca3de46bSShawn Guo 
404cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
405ca3de46bSShawn Guo 	.bind = imx_bind,
406ca3de46bSShawn Guo 	.unbind = imx_unbind,
407ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
408f5e50bf4SAndrzej Pietrasiewicz 	.change_mode = imx_change_mode,
409ca3de46bSShawn Guo 	.get_crit_temp = imx_get_crit_temp,
410017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
411ca3de46bSShawn Guo };
412ca3de46bSShawn Guo 
413e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
414ca3de46bSShawn Guo {
415ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
4164e5f61caSUwe Kleine-König 	int n1;
417749e8be7SAnson Huang 	u64 temp64;
418ca3de46bSShawn Guo 
419e4bb2240SUwe Kleine-König 	if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
420ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
421ca3de46bSShawn Guo 		return -EINVAL;
422ca3de46bSShawn Guo 	}
423ca3de46bSShawn Guo 
424ca3de46bSShawn Guo 	/*
425f085f672SAnson Huang 	 * On i.MX7D, we only use the calibration data at 25C to get the temp,
426f085f672SAnson Huang 	 * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
427f085f672SAnson Huang 	 */
428f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D) {
429f085f672SAnson Huang 		data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
430f085f672SAnson Huang 		return 0;
431f085f672SAnson Huang 	}
432f085f672SAnson Huang 
433f085f672SAnson Huang 	/*
434c5bbdb4bSUwe Kleine-König 	 * The sensor is calibrated at 25 °C (aka T1) and the value measured
435c5bbdb4bSUwe Kleine-König 	 * (aka N1) at this temperature is provided in bits [31:20] in the
436c5bbdb4bSUwe Kleine-König 	 * i.MX's OCOTP value ANA1.
437c5bbdb4bSUwe Kleine-König 	 * To find the actual temperature T, the following formula has to be used
438c5bbdb4bSUwe Kleine-König 	 * when reading value n from the sensor:
439c5bbdb4bSUwe Kleine-König 	 *
4404e5f61caSUwe Kleine-König 	 * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
4414e5f61caSUwe Kleine-König 	 *   = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
4424e5f61caSUwe Kleine-König 	 *   = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
443c5bbdb4bSUwe Kleine-König 	 *   = c2 - c1 * N
444c5bbdb4bSUwe Kleine-König 	 *
445c5bbdb4bSUwe Kleine-König 	 * with
446c5bbdb4bSUwe Kleine-König 	 *
4474e5f61caSUwe Kleine-König 	 *  T1' = 28.580661 °C
4484e5f61caSUwe Kleine-König 	 *   c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
4494e5f61caSUwe Kleine-König 	 *   c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
4504e5f61caSUwe Kleine-König 	 *      = T1' + N1 * c1
451ca3de46bSShawn Guo 	 */
452e4bb2240SUwe Kleine-König 	n1 = ocotp_ana1 >> 20;
453ca3de46bSShawn Guo 
4544e5f61caSUwe Kleine-König 	temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
455c5bbdb4bSUwe Kleine-König 	temp64 *= 1000; /* to get result in °mC */
4564e5f61caSUwe Kleine-König 	do_div(temp64, 15423 * n1 - 4148468);
457749e8be7SAnson Huang 	data->c1 = temp64;
4584e5f61caSUwe Kleine-König 	data->c2 = n1 * data->c1 + 28581;
459ca3de46bSShawn Guo 
460ae621557SLeonard Crestez 	return 0;
461a2291badSTim Harvey }
462a2291badSTim Harvey 
463e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
464ae621557SLeonard Crestez {
465ae621557SLeonard Crestez 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
466ae621557SLeonard Crestez 
467a2291badSTim Harvey 	/* The maximum die temp is specified by the Temperature Grade */
468e4bb2240SUwe Kleine-König 	switch ((ocotp_mem0 >> 6) & 0x3) {
469339d7492SUwe Kleine-König 	case 0: /* Commercial (0 to 95 °C) */
470a2291badSTim Harvey 		data->temp_grade = "Commercial";
471a2291badSTim Harvey 		data->temp_max = 95000;
472a2291badSTim Harvey 		break;
473339d7492SUwe Kleine-König 	case 1: /* Extended Commercial (-20 °C to 105 °C) */
474a2291badSTim Harvey 		data->temp_grade = "Extended Commercial";
475a2291badSTim Harvey 		data->temp_max = 105000;
476a2291badSTim Harvey 		break;
477339d7492SUwe Kleine-König 	case 2: /* Industrial (-40 °C to 105 °C) */
478a2291badSTim Harvey 		data->temp_grade = "Industrial";
479a2291badSTim Harvey 		data->temp_max = 105000;
480a2291badSTim Harvey 		break;
481339d7492SUwe Kleine-König 	case 3: /* Automotive (-40 °C to 125 °C) */
482a2291badSTim Harvey 		data->temp_grade = "Automotive";
483a2291badSTim Harvey 		data->temp_max = 125000;
484a2291badSTim Harvey 		break;
485a2291badSTim Harvey 	}
486017e5142SPhilipp Zabel 
487017e5142SPhilipp Zabel 	/*
488339d7492SUwe Kleine-König 	 * Set the critical trip point at 5 °C under max
489339d7492SUwe Kleine-König 	 * Set the passive trip point at 10 °C under max (changeable via sysfs)
490017e5142SPhilipp Zabel 	 */
49130233a22SDaniel Lezcano 	trips[IMX_TRIP_PASSIVE].temperature = data->temp_max - (1000 * 10);
49230233a22SDaniel Lezcano 	trips[IMX_TRIP_CRITICAL].temperature = data->temp_max - (1000 * 5);
493ae621557SLeonard Crestez }
494ae621557SLeonard Crestez 
495ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
496ae621557SLeonard Crestez {
497ae621557SLeonard Crestez 	struct regmap *map;
498ae621557SLeonard Crestez 	int ret;
499ae621557SLeonard Crestez 	u32 val;
500ae621557SLeonard Crestez 
501ae621557SLeonard Crestez 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
502ae621557SLeonard Crestez 					      "fsl,tempmon-data");
503ae621557SLeonard Crestez 	if (IS_ERR(map)) {
504ae621557SLeonard Crestez 		ret = PTR_ERR(map);
505ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
506ae621557SLeonard Crestez 		return ret;
507ae621557SLeonard Crestez 	}
508ae621557SLeonard Crestez 
509ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_ANA1, &val);
510ae621557SLeonard Crestez 	if (ret) {
511ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
512ae621557SLeonard Crestez 		return ret;
513ae621557SLeonard Crestez 	}
514ae621557SLeonard Crestez 	ret = imx_init_calib(pdev, val);
515ae621557SLeonard Crestez 	if (ret)
516ae621557SLeonard Crestez 		return ret;
517ae621557SLeonard Crestez 
518ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_MEM0, &val);
519ae621557SLeonard Crestez 	if (ret) {
520ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
521ae621557SLeonard Crestez 		return ret;
522ae621557SLeonard Crestez 	}
523ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
524ae621557SLeonard Crestez 
525ae621557SLeonard Crestez 	return 0;
526ae621557SLeonard Crestez }
527ae621557SLeonard Crestez 
528ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
529ae621557SLeonard Crestez {
530ae621557SLeonard Crestez 	int ret;
531ae621557SLeonard Crestez 	u32 val;
532ae621557SLeonard Crestez 
533ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
534ae621557SLeonard Crestez 	if (ret)
535ae621557SLeonard Crestez 		return ret;
536be926ceeSJean-Christophe Dubois 
537be926ceeSJean-Christophe Dubois 	ret = imx_init_calib(pdev, val);
538be926ceeSJean-Christophe Dubois 	if (ret)
539be926ceeSJean-Christophe Dubois 		return ret;
540ae621557SLeonard Crestez 
541ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
542ae621557SLeonard Crestez 	if (ret)
543ae621557SLeonard Crestez 		return ret;
544ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
545017e5142SPhilipp Zabel 
546ca3de46bSShawn Guo 	return 0;
547ca3de46bSShawn Guo }
548ca3de46bSShawn Guo 
54937713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
55037713a1eSPhilipp Zabel {
55137713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
55237713a1eSPhilipp Zabel 
55337713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
55437713a1eSPhilipp Zabel 	data->irq_enabled = false;
55537713a1eSPhilipp Zabel 
55637713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
55737713a1eSPhilipp Zabel }
55837713a1eSPhilipp Zabel 
55937713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
56037713a1eSPhilipp Zabel {
56137713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
56237713a1eSPhilipp Zabel 
56317e8351aSSascha Hauer 	dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n",
56437713a1eSPhilipp Zabel 		data->alarm_temp / 1000);
56537713a1eSPhilipp Zabel 
5660e70f466SSrinivas Pandruvada 	thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
56737713a1eSPhilipp Zabel 
56837713a1eSPhilipp Zabel 	return IRQ_HANDLED;
56937713a1eSPhilipp Zabel }
57037713a1eSPhilipp Zabel 
5713c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
5723c94f17eSAnson Huang 	{ .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
5733c94f17eSAnson Huang 	{ .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
574f085f672SAnson Huang 	{ .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
5753c94f17eSAnson Huang 	{ /* end */ }
5763c94f17eSAnson Huang };
5773c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
5783c94f17eSAnson Huang 
579c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ
580a1d00154SBastian Stender /*
581a1d00154SBastian Stender  * Create cooling device in case no #cooling-cells property is available in
582a1d00154SBastian Stender  * CPU node
583a1d00154SBastian Stender  */
584a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
585a1d00154SBastian Stender {
586c589c566SAnson Huang 	struct device_node *np;
587b45fd13bSAnson Huang 	int ret = 0;
588a1d00154SBastian Stender 
589c589c566SAnson Huang 	data->policy = cpufreq_cpu_get(0);
590c589c566SAnson Huang 	if (!data->policy) {
591c589c566SAnson Huang 		pr_debug("%s: CPUFreq policy not found\n", __func__);
592c589c566SAnson Huang 		return -EPROBE_DEFER;
593c589c566SAnson Huang 	}
594c589c566SAnson Huang 
595c589c566SAnson Huang 	np = of_get_cpu_node(data->policy->cpu, NULL);
596c589c566SAnson Huang 
597a1d00154SBastian Stender 	if (!np || !of_find_property(np, "#cooling-cells", NULL)) {
598a1d00154SBastian Stender 		data->cdev = cpufreq_cooling_register(data->policy);
599a1d00154SBastian Stender 		if (IS_ERR(data->cdev)) {
600a1d00154SBastian Stender 			ret = PTR_ERR(data->cdev);
601a1d00154SBastian Stender 			cpufreq_cpu_put(data->policy);
602a1d00154SBastian Stender 		}
603a1d00154SBastian Stender 	}
604a1d00154SBastian Stender 
605b45fd13bSAnson Huang 	of_node_put(np);
606b45fd13bSAnson Huang 
607b45fd13bSAnson Huang 	return ret;
608a1d00154SBastian Stender }
609a1d00154SBastian Stender 
610c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
611c589c566SAnson Huang {
612c589c566SAnson Huang 	cpufreq_cooling_unregister(data->cdev);
613c589c566SAnson Huang 	cpufreq_cpu_put(data->policy);
614c589c566SAnson Huang }
615c589c566SAnson Huang 
616c589c566SAnson Huang #else
617c589c566SAnson Huang 
618c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
619c589c566SAnson Huang {
620c589c566SAnson Huang 	return 0;
621c589c566SAnson Huang }
622c589c566SAnson Huang 
623c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
624c589c566SAnson Huang {
625c589c566SAnson Huang }
626c589c566SAnson Huang #endif
627c589c566SAnson Huang 
628ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
629ca3de46bSShawn Guo {
630ca3de46bSShawn Guo 	struct imx_thermal_data *data;
631ca3de46bSShawn Guo 	struct regmap *map;
63237713a1eSPhilipp Zabel 	int measure_freq;
633ca3de46bSShawn Guo 	int ret;
634ca3de46bSShawn Guo 
635ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
636ca3de46bSShawn Guo 	if (!data)
637ca3de46bSShawn Guo 		return -ENOMEM;
638ca3de46bSShawn Guo 
6394cf2ddf1SOleksij Rempel 	data->dev = &pdev->dev;
6404cf2ddf1SOleksij Rempel 
641ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
642ca3de46bSShawn Guo 	if (IS_ERR(map)) {
643ca3de46bSShawn Guo 		ret = PTR_ERR(map);
644ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
645ca3de46bSShawn Guo 		return ret;
646ca3de46bSShawn Guo 	}
647ca3de46bSShawn Guo 	data->tempmon = map;
648ca3de46bSShawn Guo 
649829bc78aSCorentin LABBE 	data->socdata = of_device_get_match_data(&pdev->dev);
6508b051ec3SShailendra Verma 	if (!data->socdata) {
6518b051ec3SShailendra Verma 		dev_err(&pdev->dev, "no device match found\n");
6528b051ec3SShailendra Verma 		return -ENODEV;
6538b051ec3SShailendra Verma 	}
6543c94f17eSAnson Huang 
6553c94f17eSAnson Huang 	/* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
6563c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX) {
657f085f672SAnson Huang 		regmap_write(map, IMX6_MISC1 + REG_CLR,
658f085f672SAnson Huang 			IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
659f085f672SAnson Huang 			| IMX6_MISC1_IRQ_TEMPPANIC);
6603c94f17eSAnson Huang 		/*
6613c94f17eSAnson Huang 		 * reset value of LOW ALARM is incorrect, set it to lowest
6623c94f17eSAnson Huang 		 * value to avoid false trigger of low alarm.
6633c94f17eSAnson Huang 		 */
664f085f672SAnson Huang 		regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
665f085f672SAnson Huang 			     data->socdata->low_alarm_mask);
6663c94f17eSAnson Huang 	}
6673c94f17eSAnson Huang 
66837713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
66937713a1eSPhilipp Zabel 	if (data->irq < 0)
67037713a1eSPhilipp Zabel 		return data->irq;
67137713a1eSPhilipp Zabel 
672ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
673ca3de46bSShawn Guo 
674ae621557SLeonard Crestez 	if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) {
675ae621557SLeonard Crestez 		ret = imx_init_from_nvmem_cells(pdev);
6765f3c0200SAnson Huang 		if (ret)
6775f3c0200SAnson Huang 			return dev_err_probe(&pdev->dev, ret,
6785f3c0200SAnson Huang 					     "failed to init from nvmem\n");
679ae621557SLeonard Crestez 	} else {
680ae621557SLeonard Crestez 		ret = imx_init_from_tempmon_data(pdev);
681ae621557SLeonard Crestez 		if (ret) {
682337a4aecSAnson Huang 			dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
683ae621557SLeonard Crestez 			return ret;
684ae621557SLeonard Crestez 		}
685ca3de46bSShawn Guo 	}
686ca3de46bSShawn Guo 
687ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
688f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
689f085f672SAnson Huang 		     data->socdata->power_down_mask);
690f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
691f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
692f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
693f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
694f085f672SAnson Huang 	if (data->socdata->version != TEMPMON_IMX7D)
695f085f672SAnson Huang 		regmap_write(map, IMX6_MISC0 + REG_SET,
696f085f672SAnson Huang 			IMX6_MISC0_REFTOP_SELBIASOFF);
697f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
698f085f672SAnson Huang 		     data->socdata->power_down_mask);
699ca3de46bSShawn Guo 
700a1d00154SBastian Stender 	ret = imx_thermal_register_legacy_cooling(data);
7015f3c0200SAnson Huang 	if (ret)
7025f3c0200SAnson Huang 		return dev_err_probe(&pdev->dev, ret,
7035f3c0200SAnson Huang 				     "failed to register cpufreq cooling device\n");
704ca3de46bSShawn Guo 
70590a21ff5SHeiner Kallweit 	data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
70690a21ff5SHeiner Kallweit 	if (IS_ERR(data->thermal_clk)) {
70790a21ff5SHeiner Kallweit 		ret = PTR_ERR(data->thermal_clk);
70890a21ff5SHeiner Kallweit 		if (ret != -EPROBE_DEFER)
70990a21ff5SHeiner Kallweit 			dev_err(&pdev->dev,
71090a21ff5SHeiner Kallweit 				"failed to get thermal clk: %d\n", ret);
711c589c566SAnson Huang 		goto legacy_cleanup;
71290a21ff5SHeiner Kallweit 	}
71390a21ff5SHeiner Kallweit 
71490a21ff5SHeiner Kallweit 	/*
71590a21ff5SHeiner Kallweit 	 * Thermal sensor needs clk on to get correct value, normally
71690a21ff5SHeiner Kallweit 	 * we should enable its clk before taking measurement and disable
71790a21ff5SHeiner Kallweit 	 * clk after measurement is done, but if alarm function is enabled,
71890a21ff5SHeiner Kallweit 	 * hardware will auto measure the temperature periodically, so we
71990a21ff5SHeiner Kallweit 	 * need to keep the clk always on for alarm function.
72090a21ff5SHeiner Kallweit 	 */
72190a21ff5SHeiner Kallweit 	ret = clk_prepare_enable(data->thermal_clk);
72290a21ff5SHeiner Kallweit 	if (ret) {
72390a21ff5SHeiner Kallweit 		dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
724c589c566SAnson Huang 		goto legacy_cleanup;
72590a21ff5SHeiner Kallweit 	}
72690a21ff5SHeiner Kallweit 
72730233a22SDaniel Lezcano 	data->tz = thermal_zone_device_register_with_trips("imx_thermal_zone",
72830233a22SDaniel Lezcano 							   trips,
72930233a22SDaniel Lezcano 							   ARRAY_SIZE(trips),
730017e5142SPhilipp Zabel 							   BIT(IMX_TRIP_PASSIVE), data,
731ca3de46bSShawn Guo 							   &imx_tz_ops, NULL,
732ca3de46bSShawn Guo 							   IMX_PASSIVE_DELAY,
733ca3de46bSShawn Guo 							   IMX_POLLING_DELAY);
734ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
735ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
736ca3de46bSShawn Guo 		dev_err(&pdev->dev,
737ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
738b6ad3981SAnson Huang 		goto clk_disable;
739ca3de46bSShawn Guo 	}
740ca3de46bSShawn Guo 
741a2291badSTim Harvey 	dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
742a2291badSTim Harvey 		 " critical:%dC passive:%dC\n", data->temp_grade,
74330233a22SDaniel Lezcano 		 data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000,
74430233a22SDaniel Lezcano 		 trips[IMX_TRIP_PASSIVE].temperature / 1000);
745a2291badSTim Harvey 
74637713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
747f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
748f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
74937713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
750f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
751f085f672SAnson Huang 		     measure_freq << data->socdata->measure_freq_shift);
75230233a22SDaniel Lezcano 	imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
7533c94f17eSAnson Huang 
7543c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX)
75530233a22SDaniel Lezcano 		imx_set_panic_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
7563c94f17eSAnson Huang 
757f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
758f085f672SAnson Huang 		     data->socdata->power_down_mask);
759f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
760f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
7614cf2ddf1SOleksij Rempel 	/* After power up, we need a delay before first access can be done. */
7624cf2ddf1SOleksij Rempel 	usleep_range(20, 50);
7634cf2ddf1SOleksij Rempel 
7644cf2ddf1SOleksij Rempel 	/* the core was configured and enabled just before */
7654cf2ddf1SOleksij Rempel 	pm_runtime_set_active(&pdev->dev);
7664cf2ddf1SOleksij Rempel 	pm_runtime_enable(data->dev);
7674cf2ddf1SOleksij Rempel 
7684cf2ddf1SOleksij Rempel 	ret = pm_runtime_resume_and_get(data->dev);
7694cf2ddf1SOleksij Rempel 	if (ret < 0)
7704cf2ddf1SOleksij Rempel 		goto disable_runtime_pm;
77137713a1eSPhilipp Zabel 
772cf1ba1d7SMikhail Lappo 	data->irq_enabled = true;
7737f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_enable(data->tz);
7747f4957beSAndrzej Pietrasiewicz 	if (ret)
7757f4957beSAndrzej Pietrasiewicz 		goto thermal_zone_unregister;
776cf1ba1d7SMikhail Lappo 
77784866ee5SBai Ping 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
77884866ee5SBai Ping 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
77984866ee5SBai Ping 			0, "imx_thermal", data);
78084866ee5SBai Ping 	if (ret < 0) {
78184866ee5SBai Ping 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
782b6ad3981SAnson Huang 		goto thermal_zone_unregister;
78384866ee5SBai Ping 	}
78484866ee5SBai Ping 
7854cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
7864cf2ddf1SOleksij Rempel 
787ca3de46bSShawn Guo 	return 0;
788b6ad3981SAnson Huang 
789b6ad3981SAnson Huang thermal_zone_unregister:
790b6ad3981SAnson Huang 	thermal_zone_device_unregister(data->tz);
7914cf2ddf1SOleksij Rempel disable_runtime_pm:
7924cf2ddf1SOleksij Rempel 	pm_runtime_put_noidle(data->dev);
7934cf2ddf1SOleksij Rempel 	pm_runtime_disable(data->dev);
794b6ad3981SAnson Huang clk_disable:
795b6ad3981SAnson Huang 	clk_disable_unprepare(data->thermal_clk);
796c589c566SAnson Huang legacy_cleanup:
797c589c566SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
798b6ad3981SAnson Huang 
799b6ad3981SAnson Huang 	return ret;
800ca3de46bSShawn Guo }
801ca3de46bSShawn Guo 
802ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
803ca3de46bSShawn Guo {
804ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
80537713a1eSPhilipp Zabel 
8064cf2ddf1SOleksij Rempel 	pm_runtime_put_noidle(data->dev);
8074cf2ddf1SOleksij Rempel 	pm_runtime_disable(data->dev);
808ca3de46bSShawn Guo 
809ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
8109db11010SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
811ca3de46bSShawn Guo 
812ca3de46bSShawn Guo 	return 0;
813ca3de46bSShawn Guo }
814ca3de46bSShawn Guo 
815b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev)
816ca3de46bSShawn Guo {
817ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8187f4957beSAndrzej Pietrasiewicz 	int ret;
819ca3de46bSShawn Guo 
820ca3de46bSShawn Guo 	/*
821b46cce59SAnson Huang 	 * Need to disable thermal sensor, otherwise, when thermal core
822b46cce59SAnson Huang 	 * try to get temperature before thermal sensor resume, a wrong
823b46cce59SAnson Huang 	 * temperature will be read as the thermal sensor is powered
824f5e50bf4SAndrzej Pietrasiewicz 	 * down. This is done in change_mode() operation called from
8257f4957beSAndrzej Pietrasiewicz 	 * thermal_zone_device_disable()
826ca3de46bSShawn Guo 	 */
8277f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_disable(data->tz);
8287f4957beSAndrzej Pietrasiewicz 	if (ret)
8297f4957beSAndrzej Pietrasiewicz 		return ret;
830ca3de46bSShawn Guo 
8314cf2ddf1SOleksij Rempel 	return pm_runtime_force_suspend(data->dev);
832ca3de46bSShawn Guo }
833ca3de46bSShawn Guo 
834b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev)
835ca3de46bSShawn Guo {
836b46cce59SAnson Huang 	struct imx_thermal_data *data = dev_get_drvdata(dev);
837e3bdc8d7SArvind Yadav 	int ret;
838b46cce59SAnson Huang 
8394cf2ddf1SOleksij Rempel 	ret = pm_runtime_force_resume(data->dev);
840e3bdc8d7SArvind Yadav 	if (ret)
841e3bdc8d7SArvind Yadav 		return ret;
842b46cce59SAnson Huang 	/* Enabled thermal sensor after resume */
8434cf2ddf1SOleksij Rempel 	return thermal_zone_device_enable(data->tz);
8444cf2ddf1SOleksij Rempel }
8454cf2ddf1SOleksij Rempel 
8464cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev)
8474cf2ddf1SOleksij Rempel {
8484cf2ddf1SOleksij Rempel 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8494cf2ddf1SOleksij Rempel 	const struct thermal_soc_data *socdata = data->socdata;
8504cf2ddf1SOleksij Rempel 	struct regmap *map = data->tempmon;
8514cf2ddf1SOleksij Rempel 	int ret;
8524cf2ddf1SOleksij Rempel 
8534cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8544cf2ddf1SOleksij Rempel 			   socdata->measure_temp_mask);
8557f4957beSAndrzej Pietrasiewicz 	if (ret)
8567f4957beSAndrzej Pietrasiewicz 		return ret;
857b46cce59SAnson Huang 
8584cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8594cf2ddf1SOleksij Rempel 			   socdata->power_down_mask);
8604cf2ddf1SOleksij Rempel 	if (ret)
8614cf2ddf1SOleksij Rempel 		return ret;
8624cf2ddf1SOleksij Rempel 
8634cf2ddf1SOleksij Rempel 	clk_disable_unprepare(data->thermal_clk);
8644cf2ddf1SOleksij Rempel 
865ca3de46bSShawn Guo 	return 0;
866ca3de46bSShawn Guo }
867ca3de46bSShawn Guo 
8684cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_resume(struct device *dev)
8694cf2ddf1SOleksij Rempel {
8704cf2ddf1SOleksij Rempel 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8714cf2ddf1SOleksij Rempel 	const struct thermal_soc_data *socdata = data->socdata;
8724cf2ddf1SOleksij Rempel 	struct regmap *map = data->tempmon;
8734cf2ddf1SOleksij Rempel 	int ret;
8744cf2ddf1SOleksij Rempel 
8754cf2ddf1SOleksij Rempel 	ret = clk_prepare_enable(data->thermal_clk);
8764cf2ddf1SOleksij Rempel 	if (ret)
8774cf2ddf1SOleksij Rempel 		return ret;
8784cf2ddf1SOleksij Rempel 
8794cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8804cf2ddf1SOleksij Rempel 			   socdata->power_down_mask);
8814cf2ddf1SOleksij Rempel 	if (ret)
8824cf2ddf1SOleksij Rempel 		return ret;
8834cf2ddf1SOleksij Rempel 
8844cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8854cf2ddf1SOleksij Rempel 			   socdata->measure_temp_mask);
8864cf2ddf1SOleksij Rempel 	if (ret)
8874cf2ddf1SOleksij Rempel 		return ret;
8884cf2ddf1SOleksij Rempel 
8894cf2ddf1SOleksij Rempel 	/*
8904cf2ddf1SOleksij Rempel 	 * According to the temp sensor designers, it may require up to ~17us
8914cf2ddf1SOleksij Rempel 	 * to complete a measurement.
8924cf2ddf1SOleksij Rempel 	 */
8934cf2ddf1SOleksij Rempel 	usleep_range(20, 50);
8944cf2ddf1SOleksij Rempel 
8954cf2ddf1SOleksij Rempel 	return 0;
8964cf2ddf1SOleksij Rempel }
8974cf2ddf1SOleksij Rempel 
8984cf2ddf1SOleksij Rempel static const struct dev_pm_ops imx_thermal_pm_ops = {
8994cf2ddf1SOleksij Rempel 	SET_SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume)
9004cf2ddf1SOleksij Rempel 	SET_RUNTIME_PM_OPS(imx_thermal_runtime_suspend,
9014cf2ddf1SOleksij Rempel 			   imx_thermal_runtime_resume, NULL)
9024cf2ddf1SOleksij Rempel };
903ca3de46bSShawn Guo 
904ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
905ca3de46bSShawn Guo 	.driver = {
906ca3de46bSShawn Guo 		.name	= "imx_thermal",
907ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
908ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
909ca3de46bSShawn Guo 	},
910ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
911ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
912ca3de46bSShawn Guo };
913ca3de46bSShawn Guo module_platform_driver(imx_thermal);
914ca3de46bSShawn Guo 
915ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
916ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
917ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
918ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
919