xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision 86df7d19)
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 {
2555f68d078SDaniel 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 
268abda7383SDaniel Lezcano 	if ((val & soc_data->temp_valid_mask) == 0)
269ca3de46bSShawn Guo 		return -EAGAIN;
270ca3de46bSShawn Guo 
271f085f672SAnson Huang 	n_meas = (val & soc_data->temp_value_mask)
272f085f672SAnson Huang 		>> soc_data->temp_value_shift;
273ca3de46bSShawn Guo 
274ae621557SLeonard Crestez 	/* See imx_init_calib() for formula derivation */
275f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
276f085f672SAnson Huang 		*temp = (n_meas - data->c1 + 25) * 1000;
277f085f672SAnson Huang 	else
278749e8be7SAnson Huang 		*temp = data->c2 - n_meas * data->c1;
279ca3de46bSShawn Guo 
2803c94f17eSAnson Huang 	/* Update alarm value to next higher trip point for TEMPMON_IMX6Q */
2813c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6Q) {
28230233a22SDaniel Lezcano 		if (data->alarm_temp == trips[IMX_TRIP_PASSIVE].temperature &&
28330233a22SDaniel Lezcano 			*temp >= trips[IMX_TRIP_PASSIVE].temperature)
28430233a22SDaniel Lezcano 			imx_set_alarm_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
28530233a22SDaniel Lezcano 		if (data->alarm_temp == trips[IMX_TRIP_CRITICAL].temperature &&
28630233a22SDaniel Lezcano 			*temp < trips[IMX_TRIP_PASSIVE].temperature) {
28730233a22SDaniel Lezcano 			imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
288dec07d39SDaniel Lezcano 			dev_dbg(data->dev, "thermal alarm off: T < %d\n",
28937713a1eSPhilipp Zabel 				data->alarm_temp / 1000);
29037713a1eSPhilipp Zabel 		}
2913c94f17eSAnson Huang 	}
29237713a1eSPhilipp Zabel 
29337713a1eSPhilipp Zabel 	if (*temp != data->last_temp) {
294dec07d39SDaniel Lezcano 		dev_dbg(data->dev, "millicelsius: %d\n", *temp);
29537713a1eSPhilipp Zabel 		data->last_temp = *temp;
29637713a1eSPhilipp Zabel 	}
29737713a1eSPhilipp Zabel 
29837713a1eSPhilipp Zabel 	/* Reenable alarm IRQ if temperature below alarm temperature */
29937713a1eSPhilipp Zabel 	if (!data->irq_enabled && *temp < data->alarm_temp) {
30037713a1eSPhilipp Zabel 		data->irq_enabled = true;
30137713a1eSPhilipp Zabel 		enable_irq(data->irq);
302ca3de46bSShawn Guo 	}
303ca3de46bSShawn Guo 
3044cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
3054cf2ddf1SOleksij Rempel 
306ca3de46bSShawn Guo 	return 0;
307ca3de46bSShawn Guo }
308ca3de46bSShawn Guo 
309f5e50bf4SAndrzej Pietrasiewicz static int imx_change_mode(struct thermal_zone_device *tz,
310ca3de46bSShawn Guo 			   enum thermal_device_mode mode)
311ca3de46bSShawn Guo {
3125f68d078SDaniel Lezcano 	struct imx_thermal_data *data = thermal_zone_device_priv(tz);
313ca3de46bSShawn Guo 
314ca3de46bSShawn Guo 	if (mode == THERMAL_DEVICE_ENABLED) {
3154cf2ddf1SOleksij Rempel 		pm_runtime_get(data->dev);
31637713a1eSPhilipp Zabel 
31737713a1eSPhilipp Zabel 		if (!data->irq_enabled) {
31837713a1eSPhilipp Zabel 			data->irq_enabled = true;
31937713a1eSPhilipp Zabel 			enable_irq(data->irq);
32037713a1eSPhilipp Zabel 		}
321ca3de46bSShawn Guo 	} else {
3224cf2ddf1SOleksij Rempel 		pm_runtime_put(data->dev);
32337713a1eSPhilipp Zabel 
32437713a1eSPhilipp Zabel 		if (data->irq_enabled) {
32537713a1eSPhilipp Zabel 			disable_irq(data->irq);
32637713a1eSPhilipp Zabel 			data->irq_enabled = false;
32737713a1eSPhilipp Zabel 		}
328ca3de46bSShawn Guo 	}
329ca3de46bSShawn Guo 
330ca3de46bSShawn Guo 	return 0;
331ca3de46bSShawn Guo }
332ca3de46bSShawn Guo 
33317e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
334ca3de46bSShawn Guo {
33530233a22SDaniel Lezcano 	*temp = trips[IMX_TRIP_CRITICAL].temperature;
336017e5142SPhilipp Zabel 
337017e5142SPhilipp Zabel 	return 0;
338017e5142SPhilipp Zabel }
339017e5142SPhilipp Zabel 
340017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
34117e8351aSSascha Hauer 			     int temp)
342017e5142SPhilipp Zabel {
3435f68d078SDaniel Lezcano 	struct imx_thermal_data *data = thermal_zone_device_priv(tz);
3444cf2ddf1SOleksij Rempel 	int ret;
3454cf2ddf1SOleksij Rempel 
3464cf2ddf1SOleksij Rempel 	ret = pm_runtime_resume_and_get(data->dev);
3474cf2ddf1SOleksij Rempel 	if (ret < 0)
3484cf2ddf1SOleksij Rempel 		return ret;
349017e5142SPhilipp Zabel 
350a2291badSTim Harvey 	/* do not allow changing critical threshold */
351017e5142SPhilipp Zabel 	if (trip == IMX_TRIP_CRITICAL)
352017e5142SPhilipp Zabel 		return -EPERM;
353017e5142SPhilipp Zabel 
354a2291badSTim Harvey 	/* do not allow passive to be set higher than critical */
35530233a22SDaniel Lezcano 	if (temp < 0 || temp > trips[IMX_TRIP_CRITICAL].temperature)
356017e5142SPhilipp Zabel 		return -EINVAL;
357017e5142SPhilipp Zabel 
35830233a22SDaniel Lezcano 	trips[IMX_TRIP_PASSIVE].temperature = temp;
359017e5142SPhilipp Zabel 
36037713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
36137713a1eSPhilipp Zabel 
3624cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
3634cf2ddf1SOleksij Rempel 
364ca3de46bSShawn Guo 	return 0;
365ca3de46bSShawn Guo }
366ca3de46bSShawn Guo 
367ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
368ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
369ca3de46bSShawn Guo {
370dec07d39SDaniel Lezcano 	return thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
371ca3de46bSShawn Guo 						THERMAL_NO_LIMIT,
3726cd9e9f6SKapileshwar Singh 						THERMAL_NO_LIMIT,
3736cd9e9f6SKapileshwar Singh 						THERMAL_WEIGHT_DEFAULT);
374ca3de46bSShawn Guo }
375ca3de46bSShawn Guo 
376ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
377ca3de46bSShawn Guo 		      struct thermal_cooling_device *cdev)
378ca3de46bSShawn Guo {
379dec07d39SDaniel Lezcano 	return thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
380ca3de46bSShawn Guo }
381ca3de46bSShawn Guo 
382cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
383ca3de46bSShawn Guo 	.bind = imx_bind,
384ca3de46bSShawn Guo 	.unbind = imx_unbind,
385ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
386f5e50bf4SAndrzej Pietrasiewicz 	.change_mode = imx_change_mode,
387ca3de46bSShawn Guo 	.get_crit_temp = imx_get_crit_temp,
388017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
389ca3de46bSShawn Guo };
390ca3de46bSShawn Guo 
391e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
392ca3de46bSShawn Guo {
393ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
3944e5f61caSUwe Kleine-König 	int n1;
395749e8be7SAnson Huang 	u64 temp64;
396ca3de46bSShawn Guo 
397e4bb2240SUwe Kleine-König 	if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
398ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
399ca3de46bSShawn Guo 		return -EINVAL;
400ca3de46bSShawn Guo 	}
401ca3de46bSShawn Guo 
402ca3de46bSShawn Guo 	/*
403f085f672SAnson Huang 	 * On i.MX7D, we only use the calibration data at 25C to get the temp,
404f085f672SAnson Huang 	 * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
405f085f672SAnson Huang 	 */
406f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D) {
407f085f672SAnson Huang 		data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
408f085f672SAnson Huang 		return 0;
409f085f672SAnson Huang 	}
410f085f672SAnson Huang 
411f085f672SAnson Huang 	/*
412c5bbdb4bSUwe Kleine-König 	 * The sensor is calibrated at 25 °C (aka T1) and the value measured
413c5bbdb4bSUwe Kleine-König 	 * (aka N1) at this temperature is provided in bits [31:20] in the
414c5bbdb4bSUwe Kleine-König 	 * i.MX's OCOTP value ANA1.
415c5bbdb4bSUwe Kleine-König 	 * To find the actual temperature T, the following formula has to be used
416c5bbdb4bSUwe Kleine-König 	 * when reading value n from the sensor:
417c5bbdb4bSUwe Kleine-König 	 *
4184e5f61caSUwe Kleine-König 	 * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
4194e5f61caSUwe Kleine-König 	 *   = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
4204e5f61caSUwe Kleine-König 	 *   = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
421c5bbdb4bSUwe Kleine-König 	 *   = c2 - c1 * N
422c5bbdb4bSUwe Kleine-König 	 *
423c5bbdb4bSUwe Kleine-König 	 * with
424c5bbdb4bSUwe Kleine-König 	 *
4254e5f61caSUwe Kleine-König 	 *  T1' = 28.580661 °C
4264e5f61caSUwe Kleine-König 	 *   c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
4274e5f61caSUwe Kleine-König 	 *   c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
4284e5f61caSUwe Kleine-König 	 *      = T1' + N1 * c1
429ca3de46bSShawn Guo 	 */
430e4bb2240SUwe Kleine-König 	n1 = ocotp_ana1 >> 20;
431ca3de46bSShawn Guo 
4324e5f61caSUwe Kleine-König 	temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
433c5bbdb4bSUwe Kleine-König 	temp64 *= 1000; /* to get result in °mC */
4344e5f61caSUwe Kleine-König 	do_div(temp64, 15423 * n1 - 4148468);
435749e8be7SAnson Huang 	data->c1 = temp64;
4364e5f61caSUwe Kleine-König 	data->c2 = n1 * data->c1 + 28581;
437ca3de46bSShawn Guo 
438ae621557SLeonard Crestez 	return 0;
439a2291badSTim Harvey }
440a2291badSTim Harvey 
441e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
442ae621557SLeonard Crestez {
443ae621557SLeonard Crestez 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
444ae621557SLeonard Crestez 
445a2291badSTim Harvey 	/* The maximum die temp is specified by the Temperature Grade */
446e4bb2240SUwe Kleine-König 	switch ((ocotp_mem0 >> 6) & 0x3) {
447339d7492SUwe Kleine-König 	case 0: /* Commercial (0 to 95 °C) */
448a2291badSTim Harvey 		data->temp_grade = "Commercial";
449a2291badSTim Harvey 		data->temp_max = 95000;
450a2291badSTim Harvey 		break;
451339d7492SUwe Kleine-König 	case 1: /* Extended Commercial (-20 °C to 105 °C) */
452a2291badSTim Harvey 		data->temp_grade = "Extended Commercial";
453a2291badSTim Harvey 		data->temp_max = 105000;
454a2291badSTim Harvey 		break;
455339d7492SUwe Kleine-König 	case 2: /* Industrial (-40 °C to 105 °C) */
456a2291badSTim Harvey 		data->temp_grade = "Industrial";
457a2291badSTim Harvey 		data->temp_max = 105000;
458a2291badSTim Harvey 		break;
459339d7492SUwe Kleine-König 	case 3: /* Automotive (-40 °C to 125 °C) */
460a2291badSTim Harvey 		data->temp_grade = "Automotive";
461a2291badSTim Harvey 		data->temp_max = 125000;
462a2291badSTim Harvey 		break;
463a2291badSTim Harvey 	}
464017e5142SPhilipp Zabel 
465017e5142SPhilipp Zabel 	/*
466339d7492SUwe Kleine-König 	 * Set the critical trip point at 5 °C under max
467339d7492SUwe Kleine-König 	 * Set the passive trip point at 10 °C under max (changeable via sysfs)
468017e5142SPhilipp Zabel 	 */
46930233a22SDaniel Lezcano 	trips[IMX_TRIP_PASSIVE].temperature = data->temp_max - (1000 * 10);
47030233a22SDaniel Lezcano 	trips[IMX_TRIP_CRITICAL].temperature = data->temp_max - (1000 * 5);
471ae621557SLeonard Crestez }
472ae621557SLeonard Crestez 
473ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
474ae621557SLeonard Crestez {
475ae621557SLeonard Crestez 	struct regmap *map;
476ae621557SLeonard Crestez 	int ret;
477ae621557SLeonard Crestez 	u32 val;
478ae621557SLeonard Crestez 
479ae621557SLeonard Crestez 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
480ae621557SLeonard Crestez 					      "fsl,tempmon-data");
481ae621557SLeonard Crestez 	if (IS_ERR(map)) {
482ae621557SLeonard Crestez 		ret = PTR_ERR(map);
483ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
484ae621557SLeonard Crestez 		return ret;
485ae621557SLeonard Crestez 	}
486ae621557SLeonard Crestez 
487ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_ANA1, &val);
488ae621557SLeonard Crestez 	if (ret) {
489ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
490ae621557SLeonard Crestez 		return ret;
491ae621557SLeonard Crestez 	}
492ae621557SLeonard Crestez 	ret = imx_init_calib(pdev, val);
493ae621557SLeonard Crestez 	if (ret)
494ae621557SLeonard Crestez 		return ret;
495ae621557SLeonard Crestez 
496ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_MEM0, &val);
497ae621557SLeonard Crestez 	if (ret) {
498ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
499ae621557SLeonard Crestez 		return ret;
500ae621557SLeonard Crestez 	}
501ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
502ae621557SLeonard Crestez 
503ae621557SLeonard Crestez 	return 0;
504ae621557SLeonard Crestez }
505ae621557SLeonard Crestez 
506ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
507ae621557SLeonard Crestez {
508ae621557SLeonard Crestez 	int ret;
509ae621557SLeonard Crestez 	u32 val;
510ae621557SLeonard Crestez 
511ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
512ae621557SLeonard Crestez 	if (ret)
513ae621557SLeonard Crestez 		return ret;
514be926ceeSJean-Christophe Dubois 
515be926ceeSJean-Christophe Dubois 	ret = imx_init_calib(pdev, val);
516be926ceeSJean-Christophe Dubois 	if (ret)
517be926ceeSJean-Christophe Dubois 		return ret;
518ae621557SLeonard Crestez 
519ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
520ae621557SLeonard Crestez 	if (ret)
521ae621557SLeonard Crestez 		return ret;
522ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
523017e5142SPhilipp Zabel 
524ca3de46bSShawn Guo 	return 0;
525ca3de46bSShawn Guo }
526ca3de46bSShawn Guo 
52737713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
52837713a1eSPhilipp Zabel {
52937713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
53037713a1eSPhilipp Zabel 
53137713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
53237713a1eSPhilipp Zabel 	data->irq_enabled = false;
53337713a1eSPhilipp Zabel 
53437713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
53537713a1eSPhilipp Zabel }
53637713a1eSPhilipp Zabel 
53737713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
53837713a1eSPhilipp Zabel {
53937713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
54037713a1eSPhilipp Zabel 
541dec07d39SDaniel Lezcano 	dev_dbg(data->dev, "THERMAL ALARM: T > %d\n", data->alarm_temp / 1000);
54237713a1eSPhilipp Zabel 
5430e70f466SSrinivas Pandruvada 	thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
54437713a1eSPhilipp Zabel 
54537713a1eSPhilipp Zabel 	return IRQ_HANDLED;
54637713a1eSPhilipp Zabel }
54737713a1eSPhilipp Zabel 
5483c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
5493c94f17eSAnson Huang 	{ .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
5503c94f17eSAnson Huang 	{ .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
551f085f672SAnson Huang 	{ .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
5523c94f17eSAnson Huang 	{ /* end */ }
5533c94f17eSAnson Huang };
5543c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
5553c94f17eSAnson Huang 
556c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ
557a1d00154SBastian Stender /*
558a1d00154SBastian Stender  * Create cooling device in case no #cooling-cells property is available in
559a1d00154SBastian Stender  * CPU node
560a1d00154SBastian Stender  */
561a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
562a1d00154SBastian Stender {
563c589c566SAnson Huang 	struct device_node *np;
564b45fd13bSAnson Huang 	int ret = 0;
565a1d00154SBastian Stender 
566c589c566SAnson Huang 	data->policy = cpufreq_cpu_get(0);
567c589c566SAnson Huang 	if (!data->policy) {
568c589c566SAnson Huang 		pr_debug("%s: CPUFreq policy not found\n", __func__);
569c589c566SAnson Huang 		return -EPROBE_DEFER;
570c589c566SAnson Huang 	}
571c589c566SAnson Huang 
572c589c566SAnson Huang 	np = of_get_cpu_node(data->policy->cpu, NULL);
573c589c566SAnson Huang 
574*86df7d19SRob Herring 	if (!np || !of_property_present(np, "#cooling-cells")) {
575a1d00154SBastian Stender 		data->cdev = cpufreq_cooling_register(data->policy);
576a1d00154SBastian Stender 		if (IS_ERR(data->cdev)) {
577a1d00154SBastian Stender 			ret = PTR_ERR(data->cdev);
578a1d00154SBastian Stender 			cpufreq_cpu_put(data->policy);
579a1d00154SBastian Stender 		}
580a1d00154SBastian Stender 	}
581a1d00154SBastian Stender 
582b45fd13bSAnson Huang 	of_node_put(np);
583b45fd13bSAnson Huang 
584b45fd13bSAnson Huang 	return ret;
585a1d00154SBastian Stender }
586a1d00154SBastian Stender 
587c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
588c589c566SAnson Huang {
589c589c566SAnson Huang 	cpufreq_cooling_unregister(data->cdev);
590c589c566SAnson Huang 	cpufreq_cpu_put(data->policy);
591c589c566SAnson Huang }
592c589c566SAnson Huang 
593c589c566SAnson Huang #else
594c589c566SAnson Huang 
595c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
596c589c566SAnson Huang {
597c589c566SAnson Huang 	return 0;
598c589c566SAnson Huang }
599c589c566SAnson Huang 
600c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
601c589c566SAnson Huang {
602c589c566SAnson Huang }
603c589c566SAnson Huang #endif
604c589c566SAnson Huang 
605ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
606ca3de46bSShawn Guo {
607ca3de46bSShawn Guo 	struct imx_thermal_data *data;
608ca3de46bSShawn Guo 	struct regmap *map;
60937713a1eSPhilipp Zabel 	int measure_freq;
610ca3de46bSShawn Guo 	int ret;
611ca3de46bSShawn Guo 
612ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
613ca3de46bSShawn Guo 	if (!data)
614ca3de46bSShawn Guo 		return -ENOMEM;
615ca3de46bSShawn Guo 
6164cf2ddf1SOleksij Rempel 	data->dev = &pdev->dev;
6174cf2ddf1SOleksij Rempel 
618ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
619ca3de46bSShawn Guo 	if (IS_ERR(map)) {
620ca3de46bSShawn Guo 		ret = PTR_ERR(map);
621ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
622ca3de46bSShawn Guo 		return ret;
623ca3de46bSShawn Guo 	}
624ca3de46bSShawn Guo 	data->tempmon = map;
625ca3de46bSShawn Guo 
626829bc78aSCorentin LABBE 	data->socdata = of_device_get_match_data(&pdev->dev);
6278b051ec3SShailendra Verma 	if (!data->socdata) {
6288b051ec3SShailendra Verma 		dev_err(&pdev->dev, "no device match found\n");
6298b051ec3SShailendra Verma 		return -ENODEV;
6308b051ec3SShailendra Verma 	}
6313c94f17eSAnson Huang 
6323c94f17eSAnson Huang 	/* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
6333c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX) {
634f085f672SAnson Huang 		regmap_write(map, IMX6_MISC1 + REG_CLR,
635f085f672SAnson Huang 			IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
636f085f672SAnson Huang 			| IMX6_MISC1_IRQ_TEMPPANIC);
6373c94f17eSAnson Huang 		/*
6383c94f17eSAnson Huang 		 * reset value of LOW ALARM is incorrect, set it to lowest
6393c94f17eSAnson Huang 		 * value to avoid false trigger of low alarm.
6403c94f17eSAnson Huang 		 */
641f085f672SAnson Huang 		regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
642f085f672SAnson Huang 			     data->socdata->low_alarm_mask);
6433c94f17eSAnson Huang 	}
6443c94f17eSAnson Huang 
64537713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
64637713a1eSPhilipp Zabel 	if (data->irq < 0)
64737713a1eSPhilipp Zabel 		return data->irq;
64837713a1eSPhilipp Zabel 
649ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
650ca3de46bSShawn Guo 
651*86df7d19SRob Herring 	if (of_property_present(pdev->dev.of_node, "nvmem-cells")) {
652ae621557SLeonard Crestez 		ret = imx_init_from_nvmem_cells(pdev);
6535f3c0200SAnson Huang 		if (ret)
6545f3c0200SAnson Huang 			return dev_err_probe(&pdev->dev, ret,
6555f3c0200SAnson Huang 					     "failed to init from nvmem\n");
656ae621557SLeonard Crestez 	} else {
657ae621557SLeonard Crestez 		ret = imx_init_from_tempmon_data(pdev);
658ae621557SLeonard Crestez 		if (ret) {
659337a4aecSAnson Huang 			dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
660ae621557SLeonard Crestez 			return ret;
661ae621557SLeonard Crestez 		}
662ca3de46bSShawn Guo 	}
663ca3de46bSShawn Guo 
664ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
665f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
666f085f672SAnson Huang 		     data->socdata->power_down_mask);
667f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
668f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
669f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
670f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
671f085f672SAnson Huang 	if (data->socdata->version != TEMPMON_IMX7D)
672f085f672SAnson Huang 		regmap_write(map, IMX6_MISC0 + REG_SET,
673f085f672SAnson Huang 			IMX6_MISC0_REFTOP_SELBIASOFF);
674f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
675f085f672SAnson Huang 		     data->socdata->power_down_mask);
676ca3de46bSShawn Guo 
677a1d00154SBastian Stender 	ret = imx_thermal_register_legacy_cooling(data);
6785f3c0200SAnson Huang 	if (ret)
6795f3c0200SAnson Huang 		return dev_err_probe(&pdev->dev, ret,
6805f3c0200SAnson Huang 				     "failed to register cpufreq cooling device\n");
681ca3de46bSShawn Guo 
68290a21ff5SHeiner Kallweit 	data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
68390a21ff5SHeiner Kallweit 	if (IS_ERR(data->thermal_clk)) {
68490a21ff5SHeiner Kallweit 		ret = PTR_ERR(data->thermal_clk);
68590a21ff5SHeiner Kallweit 		if (ret != -EPROBE_DEFER)
68690a21ff5SHeiner Kallweit 			dev_err(&pdev->dev,
68790a21ff5SHeiner Kallweit 				"failed to get thermal clk: %d\n", ret);
688c589c566SAnson Huang 		goto legacy_cleanup;
68990a21ff5SHeiner Kallweit 	}
69090a21ff5SHeiner Kallweit 
69190a21ff5SHeiner Kallweit 	/*
69290a21ff5SHeiner Kallweit 	 * Thermal sensor needs clk on to get correct value, normally
69390a21ff5SHeiner Kallweit 	 * we should enable its clk before taking measurement and disable
69490a21ff5SHeiner Kallweit 	 * clk after measurement is done, but if alarm function is enabled,
69590a21ff5SHeiner Kallweit 	 * hardware will auto measure the temperature periodically, so we
69690a21ff5SHeiner Kallweit 	 * need to keep the clk always on for alarm function.
69790a21ff5SHeiner Kallweit 	 */
69890a21ff5SHeiner Kallweit 	ret = clk_prepare_enable(data->thermal_clk);
69990a21ff5SHeiner Kallweit 	if (ret) {
70090a21ff5SHeiner Kallweit 		dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
701c589c566SAnson Huang 		goto legacy_cleanup;
70290a21ff5SHeiner Kallweit 	}
70390a21ff5SHeiner Kallweit 
70430233a22SDaniel Lezcano 	data->tz = thermal_zone_device_register_with_trips("imx_thermal_zone",
70530233a22SDaniel Lezcano 							   trips,
70630233a22SDaniel Lezcano 							   ARRAY_SIZE(trips),
707017e5142SPhilipp Zabel 							   BIT(IMX_TRIP_PASSIVE), data,
708ca3de46bSShawn Guo 							   &imx_tz_ops, NULL,
709ca3de46bSShawn Guo 							   IMX_PASSIVE_DELAY,
710ca3de46bSShawn Guo 							   IMX_POLLING_DELAY);
711ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
712ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
713ca3de46bSShawn Guo 		dev_err(&pdev->dev,
714ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
715b6ad3981SAnson Huang 		goto clk_disable;
716ca3de46bSShawn Guo 	}
717ca3de46bSShawn Guo 
718a2291badSTim Harvey 	dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
719a2291badSTim Harvey 		 " critical:%dC passive:%dC\n", data->temp_grade,
72030233a22SDaniel Lezcano 		 data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000,
72130233a22SDaniel Lezcano 		 trips[IMX_TRIP_PASSIVE].temperature / 1000);
722a2291badSTim Harvey 
72337713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
724f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
725f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
72637713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
727f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
728f085f672SAnson Huang 		     measure_freq << data->socdata->measure_freq_shift);
72930233a22SDaniel Lezcano 	imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
7303c94f17eSAnson Huang 
7313c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX)
73230233a22SDaniel Lezcano 		imx_set_panic_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
7333c94f17eSAnson Huang 
734f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
735f085f672SAnson Huang 		     data->socdata->power_down_mask);
736f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
737f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
7384cf2ddf1SOleksij Rempel 	/* After power up, we need a delay before first access can be done. */
7394cf2ddf1SOleksij Rempel 	usleep_range(20, 50);
7404cf2ddf1SOleksij Rempel 
7414cf2ddf1SOleksij Rempel 	/* the core was configured and enabled just before */
7424cf2ddf1SOleksij Rempel 	pm_runtime_set_active(&pdev->dev);
7434cf2ddf1SOleksij Rempel 	pm_runtime_enable(data->dev);
7444cf2ddf1SOleksij Rempel 
7454cf2ddf1SOleksij Rempel 	ret = pm_runtime_resume_and_get(data->dev);
7464cf2ddf1SOleksij Rempel 	if (ret < 0)
7474cf2ddf1SOleksij Rempel 		goto disable_runtime_pm;
74837713a1eSPhilipp Zabel 
749cf1ba1d7SMikhail Lappo 	data->irq_enabled = true;
7507f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_enable(data->tz);
7517f4957beSAndrzej Pietrasiewicz 	if (ret)
7527f4957beSAndrzej Pietrasiewicz 		goto thermal_zone_unregister;
753cf1ba1d7SMikhail Lappo 
75484866ee5SBai Ping 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
75584866ee5SBai Ping 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
75684866ee5SBai Ping 			0, "imx_thermal", data);
75784866ee5SBai Ping 	if (ret < 0) {
75884866ee5SBai Ping 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
759b6ad3981SAnson Huang 		goto thermal_zone_unregister;
76084866ee5SBai Ping 	}
76184866ee5SBai Ping 
7624cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
7634cf2ddf1SOleksij Rempel 
764ca3de46bSShawn Guo 	return 0;
765b6ad3981SAnson Huang 
766b6ad3981SAnson Huang thermal_zone_unregister:
767b6ad3981SAnson Huang 	thermal_zone_device_unregister(data->tz);
7684cf2ddf1SOleksij Rempel disable_runtime_pm:
7694cf2ddf1SOleksij Rempel 	pm_runtime_put_noidle(data->dev);
7704cf2ddf1SOleksij Rempel 	pm_runtime_disable(data->dev);
771b6ad3981SAnson Huang clk_disable:
772b6ad3981SAnson Huang 	clk_disable_unprepare(data->thermal_clk);
773c589c566SAnson Huang legacy_cleanup:
774c589c566SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
775b6ad3981SAnson Huang 
776b6ad3981SAnson Huang 	return ret;
777ca3de46bSShawn Guo }
778ca3de46bSShawn Guo 
779ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
780ca3de46bSShawn Guo {
781ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
78237713a1eSPhilipp Zabel 
7834cf2ddf1SOleksij Rempel 	pm_runtime_put_noidle(data->dev);
7844cf2ddf1SOleksij Rempel 	pm_runtime_disable(data->dev);
785ca3de46bSShawn Guo 
786ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
7879db11010SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
788ca3de46bSShawn Guo 
789ca3de46bSShawn Guo 	return 0;
790ca3de46bSShawn Guo }
791ca3de46bSShawn Guo 
792b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev)
793ca3de46bSShawn Guo {
794ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
7957f4957beSAndrzej Pietrasiewicz 	int ret;
796ca3de46bSShawn Guo 
797ca3de46bSShawn Guo 	/*
798b46cce59SAnson Huang 	 * Need to disable thermal sensor, otherwise, when thermal core
799b46cce59SAnson Huang 	 * try to get temperature before thermal sensor resume, a wrong
800b46cce59SAnson Huang 	 * temperature will be read as the thermal sensor is powered
801f5e50bf4SAndrzej Pietrasiewicz 	 * down. This is done in change_mode() operation called from
8027f4957beSAndrzej Pietrasiewicz 	 * thermal_zone_device_disable()
803ca3de46bSShawn Guo 	 */
8047f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_disable(data->tz);
8057f4957beSAndrzej Pietrasiewicz 	if (ret)
8067f4957beSAndrzej Pietrasiewicz 		return ret;
807ca3de46bSShawn Guo 
8084cf2ddf1SOleksij Rempel 	return pm_runtime_force_suspend(data->dev);
809ca3de46bSShawn Guo }
810ca3de46bSShawn Guo 
811b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev)
812ca3de46bSShawn Guo {
813b46cce59SAnson Huang 	struct imx_thermal_data *data = dev_get_drvdata(dev);
814e3bdc8d7SArvind Yadav 	int ret;
815b46cce59SAnson Huang 
8164cf2ddf1SOleksij Rempel 	ret = pm_runtime_force_resume(data->dev);
817e3bdc8d7SArvind Yadav 	if (ret)
818e3bdc8d7SArvind Yadav 		return ret;
819b46cce59SAnson Huang 	/* Enabled thermal sensor after resume */
8204cf2ddf1SOleksij Rempel 	return thermal_zone_device_enable(data->tz);
8214cf2ddf1SOleksij Rempel }
8224cf2ddf1SOleksij Rempel 
8234cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev)
8244cf2ddf1SOleksij Rempel {
8254cf2ddf1SOleksij Rempel 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8264cf2ddf1SOleksij Rempel 	const struct thermal_soc_data *socdata = data->socdata;
8274cf2ddf1SOleksij Rempel 	struct regmap *map = data->tempmon;
8284cf2ddf1SOleksij Rempel 	int ret;
8294cf2ddf1SOleksij Rempel 
8304cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8314cf2ddf1SOleksij Rempel 			   socdata->measure_temp_mask);
8327f4957beSAndrzej Pietrasiewicz 	if (ret)
8337f4957beSAndrzej Pietrasiewicz 		return ret;
834b46cce59SAnson Huang 
8354cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8364cf2ddf1SOleksij Rempel 			   socdata->power_down_mask);
8374cf2ddf1SOleksij Rempel 	if (ret)
8384cf2ddf1SOleksij Rempel 		return ret;
8394cf2ddf1SOleksij Rempel 
8404cf2ddf1SOleksij Rempel 	clk_disable_unprepare(data->thermal_clk);
8414cf2ddf1SOleksij Rempel 
842ca3de46bSShawn Guo 	return 0;
843ca3de46bSShawn Guo }
844ca3de46bSShawn Guo 
8454cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_resume(struct device *dev)
8464cf2ddf1SOleksij Rempel {
8474cf2ddf1SOleksij Rempel 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8484cf2ddf1SOleksij Rempel 	const struct thermal_soc_data *socdata = data->socdata;
8494cf2ddf1SOleksij Rempel 	struct regmap *map = data->tempmon;
8504cf2ddf1SOleksij Rempel 	int ret;
8514cf2ddf1SOleksij Rempel 
8524cf2ddf1SOleksij Rempel 	ret = clk_prepare_enable(data->thermal_clk);
8534cf2ddf1SOleksij Rempel 	if (ret)
8544cf2ddf1SOleksij Rempel 		return ret;
8554cf2ddf1SOleksij Rempel 
8564cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8574cf2ddf1SOleksij Rempel 			   socdata->power_down_mask);
8584cf2ddf1SOleksij Rempel 	if (ret)
8594cf2ddf1SOleksij Rempel 		return ret;
8604cf2ddf1SOleksij Rempel 
8614cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8624cf2ddf1SOleksij Rempel 			   socdata->measure_temp_mask);
8634cf2ddf1SOleksij Rempel 	if (ret)
8644cf2ddf1SOleksij Rempel 		return ret;
8654cf2ddf1SOleksij Rempel 
8664cf2ddf1SOleksij Rempel 	/*
8674cf2ddf1SOleksij Rempel 	 * According to the temp sensor designers, it may require up to ~17us
8684cf2ddf1SOleksij Rempel 	 * to complete a measurement.
8694cf2ddf1SOleksij Rempel 	 */
8704cf2ddf1SOleksij Rempel 	usleep_range(20, 50);
8714cf2ddf1SOleksij Rempel 
8724cf2ddf1SOleksij Rempel 	return 0;
8734cf2ddf1SOleksij Rempel }
8744cf2ddf1SOleksij Rempel 
8754cf2ddf1SOleksij Rempel static const struct dev_pm_ops imx_thermal_pm_ops = {
8764cf2ddf1SOleksij Rempel 	SET_SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume)
8774cf2ddf1SOleksij Rempel 	SET_RUNTIME_PM_OPS(imx_thermal_runtime_suspend,
8784cf2ddf1SOleksij Rempel 			   imx_thermal_runtime_resume, NULL)
8794cf2ddf1SOleksij Rempel };
880ca3de46bSShawn Guo 
881ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
882ca3de46bSShawn Guo 	.driver = {
883ca3de46bSShawn Guo 		.name	= "imx_thermal",
884ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
885ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
886ca3de46bSShawn Guo 	},
887ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
888ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
889ca3de46bSShawn Guo };
890ca3de46bSShawn Guo module_platform_driver(imx_thermal);
891ca3de46bSShawn Guo 
892ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
893ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
894ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
895ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
896