xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision f6a756e8)
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>
14*f6a756e8SRob Herring #include <linux/platform_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 
imx_set_panic_temp(struct imx_thermal_data * data,int panic_temp)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 
imx_set_alarm_temp(struct imx_thermal_data * data,int alarm_temp)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 
imx_get_temp(struct thermal_zone_device * tz,int * temp)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 
imx_change_mode(struct thermal_zone_device * tz,enum thermal_device_mode mode)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 
imx_set_trip_temp(struct thermal_zone_device * tz,int trip_id,int temp)333ed4b51b8SDaniel Lezcano static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip_id,
33417e8351aSSascha Hauer 			     int temp)
335017e5142SPhilipp Zabel {
3365f68d078SDaniel Lezcano 	struct imx_thermal_data *data = thermal_zone_device_priv(tz);
337ed4b51b8SDaniel Lezcano 	struct thermal_trip trip;
3384cf2ddf1SOleksij Rempel 	int ret;
3394cf2ddf1SOleksij Rempel 
3404cf2ddf1SOleksij Rempel 	ret = pm_runtime_resume_and_get(data->dev);
3414cf2ddf1SOleksij Rempel 	if (ret < 0)
3424cf2ddf1SOleksij Rempel 		return ret;
343017e5142SPhilipp Zabel 
344ed4b51b8SDaniel Lezcano 	ret = __thermal_zone_get_trip(tz, trip_id, &trip);
345ed4b51b8SDaniel Lezcano 	if (ret)
346ed4b51b8SDaniel Lezcano 		return ret;
347ed4b51b8SDaniel Lezcano 
348a2291badSTim Harvey 	/* do not allow changing critical threshold */
349ed4b51b8SDaniel Lezcano 	if (trip.type == THERMAL_TRIP_CRITICAL)
350017e5142SPhilipp Zabel 		return -EPERM;
351017e5142SPhilipp Zabel 
352a2291badSTim Harvey 	/* do not allow passive to be set higher than critical */
35330233a22SDaniel Lezcano 	if (temp < 0 || temp > trips[IMX_TRIP_CRITICAL].temperature)
354017e5142SPhilipp Zabel 		return -EINVAL;
355017e5142SPhilipp Zabel 
35637713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
35737713a1eSPhilipp Zabel 
3584cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
3594cf2ddf1SOleksij Rempel 
360ca3de46bSShawn Guo 	return 0;
361ca3de46bSShawn Guo }
362ca3de46bSShawn Guo 
imx_bind(struct thermal_zone_device * tz,struct thermal_cooling_device * cdev)363ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
364ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
365ca3de46bSShawn Guo {
366dec07d39SDaniel Lezcano 	return thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
367ca3de46bSShawn Guo 						THERMAL_NO_LIMIT,
3686cd9e9f6SKapileshwar Singh 						THERMAL_NO_LIMIT,
3696cd9e9f6SKapileshwar Singh 						THERMAL_WEIGHT_DEFAULT);
370ca3de46bSShawn Guo }
371ca3de46bSShawn Guo 
imx_unbind(struct thermal_zone_device * tz,struct thermal_cooling_device * cdev)372ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
373ca3de46bSShawn Guo 		      struct thermal_cooling_device *cdev)
374ca3de46bSShawn Guo {
375dec07d39SDaniel Lezcano 	return thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
376ca3de46bSShawn Guo }
377ca3de46bSShawn Guo 
378cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
379ca3de46bSShawn Guo 	.bind = imx_bind,
380ca3de46bSShawn Guo 	.unbind = imx_unbind,
381ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
382f5e50bf4SAndrzej Pietrasiewicz 	.change_mode = imx_change_mode,
383017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
384ca3de46bSShawn Guo };
385ca3de46bSShawn Guo 
imx_init_calib(struct platform_device * pdev,u32 ocotp_ana1)386e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
387ca3de46bSShawn Guo {
388ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
3894e5f61caSUwe Kleine-König 	int n1;
390749e8be7SAnson Huang 	u64 temp64;
391ca3de46bSShawn Guo 
392e4bb2240SUwe Kleine-König 	if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
393ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
394ca3de46bSShawn Guo 		return -EINVAL;
395ca3de46bSShawn Guo 	}
396ca3de46bSShawn Guo 
397ca3de46bSShawn Guo 	/*
398f085f672SAnson Huang 	 * On i.MX7D, we only use the calibration data at 25C to get the temp,
399f085f672SAnson Huang 	 * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
400f085f672SAnson Huang 	 */
401f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D) {
402f085f672SAnson Huang 		data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
403f085f672SAnson Huang 		return 0;
404f085f672SAnson Huang 	}
405f085f672SAnson Huang 
406f085f672SAnson Huang 	/*
407c5bbdb4bSUwe Kleine-König 	 * The sensor is calibrated at 25 °C (aka T1) and the value measured
408c5bbdb4bSUwe Kleine-König 	 * (aka N1) at this temperature is provided in bits [31:20] in the
409c5bbdb4bSUwe Kleine-König 	 * i.MX's OCOTP value ANA1.
410c5bbdb4bSUwe Kleine-König 	 * To find the actual temperature T, the following formula has to be used
411c5bbdb4bSUwe Kleine-König 	 * when reading value n from the sensor:
412c5bbdb4bSUwe Kleine-König 	 *
4134e5f61caSUwe Kleine-König 	 * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
4144e5f61caSUwe Kleine-König 	 *   = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
4154e5f61caSUwe Kleine-König 	 *   = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
416c5bbdb4bSUwe Kleine-König 	 *   = c2 - c1 * N
417c5bbdb4bSUwe Kleine-König 	 *
418c5bbdb4bSUwe Kleine-König 	 * with
419c5bbdb4bSUwe Kleine-König 	 *
4204e5f61caSUwe Kleine-König 	 *  T1' = 28.580661 °C
4214e5f61caSUwe Kleine-König 	 *   c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
4224e5f61caSUwe Kleine-König 	 *   c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
4234e5f61caSUwe Kleine-König 	 *      = T1' + N1 * c1
424ca3de46bSShawn Guo 	 */
425e4bb2240SUwe Kleine-König 	n1 = ocotp_ana1 >> 20;
426ca3de46bSShawn Guo 
4274e5f61caSUwe Kleine-König 	temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
428c5bbdb4bSUwe Kleine-König 	temp64 *= 1000; /* to get result in °mC */
4294e5f61caSUwe Kleine-König 	do_div(temp64, 15423 * n1 - 4148468);
430749e8be7SAnson Huang 	data->c1 = temp64;
4314e5f61caSUwe Kleine-König 	data->c2 = n1 * data->c1 + 28581;
432ca3de46bSShawn Guo 
433ae621557SLeonard Crestez 	return 0;
434a2291badSTim Harvey }
435a2291badSTim Harvey 
imx_init_temp_grade(struct platform_device * pdev,u32 ocotp_mem0)436e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
437ae621557SLeonard Crestez {
438ae621557SLeonard Crestez 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
439ae621557SLeonard Crestez 
440a2291badSTim Harvey 	/* The maximum die temp is specified by the Temperature Grade */
441e4bb2240SUwe Kleine-König 	switch ((ocotp_mem0 >> 6) & 0x3) {
442339d7492SUwe Kleine-König 	case 0: /* Commercial (0 to 95 °C) */
443a2291badSTim Harvey 		data->temp_grade = "Commercial";
444a2291badSTim Harvey 		data->temp_max = 95000;
445a2291badSTim Harvey 		break;
446339d7492SUwe Kleine-König 	case 1: /* Extended Commercial (-20 °C to 105 °C) */
447a2291badSTim Harvey 		data->temp_grade = "Extended Commercial";
448a2291badSTim Harvey 		data->temp_max = 105000;
449a2291badSTim Harvey 		break;
450339d7492SUwe Kleine-König 	case 2: /* Industrial (-40 °C to 105 °C) */
451a2291badSTim Harvey 		data->temp_grade = "Industrial";
452a2291badSTim Harvey 		data->temp_max = 105000;
453a2291badSTim Harvey 		break;
454339d7492SUwe Kleine-König 	case 3: /* Automotive (-40 °C to 125 °C) */
455a2291badSTim Harvey 		data->temp_grade = "Automotive";
456a2291badSTim Harvey 		data->temp_max = 125000;
457a2291badSTim Harvey 		break;
458a2291badSTim Harvey 	}
459017e5142SPhilipp Zabel 
460017e5142SPhilipp Zabel 	/*
461339d7492SUwe Kleine-König 	 * Set the critical trip point at 5 °C under max
462339d7492SUwe Kleine-König 	 * Set the passive trip point at 10 °C under max (changeable via sysfs)
463017e5142SPhilipp Zabel 	 */
46430233a22SDaniel Lezcano 	trips[IMX_TRIP_PASSIVE].temperature = data->temp_max - (1000 * 10);
46530233a22SDaniel Lezcano 	trips[IMX_TRIP_CRITICAL].temperature = data->temp_max - (1000 * 5);
466ae621557SLeonard Crestez }
467ae621557SLeonard Crestez 
imx_init_from_tempmon_data(struct platform_device * pdev)468ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
469ae621557SLeonard Crestez {
470ae621557SLeonard Crestez 	struct regmap *map;
471ae621557SLeonard Crestez 	int ret;
472ae621557SLeonard Crestez 	u32 val;
473ae621557SLeonard Crestez 
474ae621557SLeonard Crestez 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
475ae621557SLeonard Crestez 					      "fsl,tempmon-data");
476ae621557SLeonard Crestez 	if (IS_ERR(map)) {
477ae621557SLeonard Crestez 		ret = PTR_ERR(map);
478ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
479ae621557SLeonard Crestez 		return ret;
480ae621557SLeonard Crestez 	}
481ae621557SLeonard Crestez 
482ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_ANA1, &val);
483ae621557SLeonard Crestez 	if (ret) {
484ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
485ae621557SLeonard Crestez 		return ret;
486ae621557SLeonard Crestez 	}
487ae621557SLeonard Crestez 	ret = imx_init_calib(pdev, val);
488ae621557SLeonard Crestez 	if (ret)
489ae621557SLeonard Crestez 		return ret;
490ae621557SLeonard Crestez 
491ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_MEM0, &val);
492ae621557SLeonard Crestez 	if (ret) {
493ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
494ae621557SLeonard Crestez 		return ret;
495ae621557SLeonard Crestez 	}
496ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
497ae621557SLeonard Crestez 
498ae621557SLeonard Crestez 	return 0;
499ae621557SLeonard Crestez }
500ae621557SLeonard Crestez 
imx_init_from_nvmem_cells(struct platform_device * pdev)501ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
502ae621557SLeonard Crestez {
503ae621557SLeonard Crestez 	int ret;
504ae621557SLeonard Crestez 	u32 val;
505ae621557SLeonard Crestez 
506ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
507ae621557SLeonard Crestez 	if (ret)
508ae621557SLeonard Crestez 		return ret;
509be926ceeSJean-Christophe Dubois 
510be926ceeSJean-Christophe Dubois 	ret = imx_init_calib(pdev, val);
511be926ceeSJean-Christophe Dubois 	if (ret)
512be926ceeSJean-Christophe Dubois 		return ret;
513ae621557SLeonard Crestez 
514ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
515ae621557SLeonard Crestez 	if (ret)
516ae621557SLeonard Crestez 		return ret;
517ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
518017e5142SPhilipp Zabel 
519ca3de46bSShawn Guo 	return 0;
520ca3de46bSShawn Guo }
521ca3de46bSShawn Guo 
imx_thermal_alarm_irq(int irq,void * dev)52237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
52337713a1eSPhilipp Zabel {
52437713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
52537713a1eSPhilipp Zabel 
52637713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
52737713a1eSPhilipp Zabel 	data->irq_enabled = false;
52837713a1eSPhilipp Zabel 
52937713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
53037713a1eSPhilipp Zabel }
53137713a1eSPhilipp Zabel 
imx_thermal_alarm_irq_thread(int irq,void * dev)53237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
53337713a1eSPhilipp Zabel {
53437713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
53537713a1eSPhilipp Zabel 
536dec07d39SDaniel Lezcano 	dev_dbg(data->dev, "THERMAL ALARM: T > %d\n", data->alarm_temp / 1000);
53737713a1eSPhilipp Zabel 
5380e70f466SSrinivas Pandruvada 	thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
53937713a1eSPhilipp Zabel 
54037713a1eSPhilipp Zabel 	return IRQ_HANDLED;
54137713a1eSPhilipp Zabel }
54237713a1eSPhilipp Zabel 
5433c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
5443c94f17eSAnson Huang 	{ .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
5453c94f17eSAnson Huang 	{ .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
546f085f672SAnson Huang 	{ .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
5473c94f17eSAnson Huang 	{ /* end */ }
5483c94f17eSAnson Huang };
5493c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
5503c94f17eSAnson Huang 
551c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ
552a1d00154SBastian Stender /*
553a1d00154SBastian Stender  * Create cooling device in case no #cooling-cells property is available in
554a1d00154SBastian Stender  * CPU node
555a1d00154SBastian Stender  */
imx_thermal_register_legacy_cooling(struct imx_thermal_data * data)556a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
557a1d00154SBastian Stender {
558c589c566SAnson Huang 	struct device_node *np;
559b45fd13bSAnson Huang 	int ret = 0;
560a1d00154SBastian Stender 
561c589c566SAnson Huang 	data->policy = cpufreq_cpu_get(0);
562c589c566SAnson Huang 	if (!data->policy) {
563c589c566SAnson Huang 		pr_debug("%s: CPUFreq policy not found\n", __func__);
564c589c566SAnson Huang 		return -EPROBE_DEFER;
565c589c566SAnson Huang 	}
566c589c566SAnson Huang 
567c589c566SAnson Huang 	np = of_get_cpu_node(data->policy->cpu, NULL);
568c589c566SAnson Huang 
56986df7d19SRob Herring 	if (!np || !of_property_present(np, "#cooling-cells")) {
570a1d00154SBastian Stender 		data->cdev = cpufreq_cooling_register(data->policy);
571a1d00154SBastian Stender 		if (IS_ERR(data->cdev)) {
572a1d00154SBastian Stender 			ret = PTR_ERR(data->cdev);
573a1d00154SBastian Stender 			cpufreq_cpu_put(data->policy);
574a1d00154SBastian Stender 		}
575a1d00154SBastian Stender 	}
576a1d00154SBastian Stender 
577b45fd13bSAnson Huang 	of_node_put(np);
578b45fd13bSAnson Huang 
579b45fd13bSAnson Huang 	return ret;
580a1d00154SBastian Stender }
581a1d00154SBastian Stender 
imx_thermal_unregister_legacy_cooling(struct imx_thermal_data * data)582c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
583c589c566SAnson Huang {
584c589c566SAnson Huang 	cpufreq_cooling_unregister(data->cdev);
585c589c566SAnson Huang 	cpufreq_cpu_put(data->policy);
586c589c566SAnson Huang }
587c589c566SAnson Huang 
588c589c566SAnson Huang #else
589c589c566SAnson Huang 
imx_thermal_register_legacy_cooling(struct imx_thermal_data * data)590c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
591c589c566SAnson Huang {
592c589c566SAnson Huang 	return 0;
593c589c566SAnson Huang }
594c589c566SAnson Huang 
imx_thermal_unregister_legacy_cooling(struct imx_thermal_data * data)595c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
596c589c566SAnson Huang {
597c589c566SAnson Huang }
598c589c566SAnson Huang #endif
599c589c566SAnson Huang 
imx_thermal_probe(struct platform_device * pdev)600ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
601ca3de46bSShawn Guo {
602ca3de46bSShawn Guo 	struct imx_thermal_data *data;
603ca3de46bSShawn Guo 	struct regmap *map;
60437713a1eSPhilipp Zabel 	int measure_freq;
605ca3de46bSShawn Guo 	int ret;
606ca3de46bSShawn Guo 
607ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
608ca3de46bSShawn Guo 	if (!data)
609ca3de46bSShawn Guo 		return -ENOMEM;
610ca3de46bSShawn Guo 
6114cf2ddf1SOleksij Rempel 	data->dev = &pdev->dev;
6124cf2ddf1SOleksij Rempel 
613ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
614ca3de46bSShawn Guo 	if (IS_ERR(map)) {
615ca3de46bSShawn Guo 		ret = PTR_ERR(map);
616ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
617ca3de46bSShawn Guo 		return ret;
618ca3de46bSShawn Guo 	}
619ca3de46bSShawn Guo 	data->tempmon = map;
620ca3de46bSShawn Guo 
621829bc78aSCorentin LABBE 	data->socdata = of_device_get_match_data(&pdev->dev);
6228b051ec3SShailendra Verma 	if (!data->socdata) {
6238b051ec3SShailendra Verma 		dev_err(&pdev->dev, "no device match found\n");
6248b051ec3SShailendra Verma 		return -ENODEV;
6258b051ec3SShailendra Verma 	}
6263c94f17eSAnson Huang 
6273c94f17eSAnson Huang 	/* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
6283c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX) {
629f085f672SAnson Huang 		regmap_write(map, IMX6_MISC1 + REG_CLR,
630f085f672SAnson Huang 			IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
631f085f672SAnson Huang 			| IMX6_MISC1_IRQ_TEMPPANIC);
6323c94f17eSAnson Huang 		/*
6333c94f17eSAnson Huang 		 * reset value of LOW ALARM is incorrect, set it to lowest
6343c94f17eSAnson Huang 		 * value to avoid false trigger of low alarm.
6353c94f17eSAnson Huang 		 */
636f085f672SAnson Huang 		regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
637f085f672SAnson Huang 			     data->socdata->low_alarm_mask);
6383c94f17eSAnson Huang 	}
6393c94f17eSAnson Huang 
64037713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
64137713a1eSPhilipp Zabel 	if (data->irq < 0)
64237713a1eSPhilipp Zabel 		return data->irq;
64337713a1eSPhilipp Zabel 
644ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
645ca3de46bSShawn Guo 
64686df7d19SRob Herring 	if (of_property_present(pdev->dev.of_node, "nvmem-cells")) {
647ae621557SLeonard Crestez 		ret = imx_init_from_nvmem_cells(pdev);
6485f3c0200SAnson Huang 		if (ret)
6495f3c0200SAnson Huang 			return dev_err_probe(&pdev->dev, ret,
6505f3c0200SAnson Huang 					     "failed to init from nvmem\n");
651ae621557SLeonard Crestez 	} else {
652ae621557SLeonard Crestez 		ret = imx_init_from_tempmon_data(pdev);
653ae621557SLeonard Crestez 		if (ret) {
654337a4aecSAnson Huang 			dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
655ae621557SLeonard Crestez 			return ret;
656ae621557SLeonard Crestez 		}
657ca3de46bSShawn Guo 	}
658ca3de46bSShawn Guo 
659ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
660f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
661f085f672SAnson Huang 		     data->socdata->power_down_mask);
662f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
663f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
664f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
665f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
666f085f672SAnson Huang 	if (data->socdata->version != TEMPMON_IMX7D)
667f085f672SAnson Huang 		regmap_write(map, IMX6_MISC0 + REG_SET,
668f085f672SAnson Huang 			IMX6_MISC0_REFTOP_SELBIASOFF);
669f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
670f085f672SAnson Huang 		     data->socdata->power_down_mask);
671ca3de46bSShawn Guo 
672a1d00154SBastian Stender 	ret = imx_thermal_register_legacy_cooling(data);
6735f3c0200SAnson Huang 	if (ret)
6745f3c0200SAnson Huang 		return dev_err_probe(&pdev->dev, ret,
6755f3c0200SAnson Huang 				     "failed to register cpufreq cooling device\n");
676ca3de46bSShawn Guo 
67790a21ff5SHeiner Kallweit 	data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
67890a21ff5SHeiner Kallweit 	if (IS_ERR(data->thermal_clk)) {
67990a21ff5SHeiner Kallweit 		ret = PTR_ERR(data->thermal_clk);
68090a21ff5SHeiner Kallweit 		if (ret != -EPROBE_DEFER)
68190a21ff5SHeiner Kallweit 			dev_err(&pdev->dev,
68290a21ff5SHeiner Kallweit 				"failed to get thermal clk: %d\n", ret);
683c589c566SAnson Huang 		goto legacy_cleanup;
68490a21ff5SHeiner Kallweit 	}
68590a21ff5SHeiner Kallweit 
68690a21ff5SHeiner Kallweit 	/*
68790a21ff5SHeiner Kallweit 	 * Thermal sensor needs clk on to get correct value, normally
68890a21ff5SHeiner Kallweit 	 * we should enable its clk before taking measurement and disable
68990a21ff5SHeiner Kallweit 	 * clk after measurement is done, but if alarm function is enabled,
69090a21ff5SHeiner Kallweit 	 * hardware will auto measure the temperature periodically, so we
69190a21ff5SHeiner Kallweit 	 * need to keep the clk always on for alarm function.
69290a21ff5SHeiner Kallweit 	 */
69390a21ff5SHeiner Kallweit 	ret = clk_prepare_enable(data->thermal_clk);
69490a21ff5SHeiner Kallweit 	if (ret) {
69590a21ff5SHeiner Kallweit 		dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
696c589c566SAnson Huang 		goto legacy_cleanup;
69790a21ff5SHeiner Kallweit 	}
69890a21ff5SHeiner Kallweit 
69930233a22SDaniel Lezcano 	data->tz = thermal_zone_device_register_with_trips("imx_thermal_zone",
70030233a22SDaniel Lezcano 							   trips,
70130233a22SDaniel Lezcano 							   ARRAY_SIZE(trips),
702017e5142SPhilipp Zabel 							   BIT(IMX_TRIP_PASSIVE), data,
703ca3de46bSShawn Guo 							   &imx_tz_ops, NULL,
704ca3de46bSShawn Guo 							   IMX_PASSIVE_DELAY,
705ca3de46bSShawn Guo 							   IMX_POLLING_DELAY);
706ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
707ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
708ca3de46bSShawn Guo 		dev_err(&pdev->dev,
709ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
710b6ad3981SAnson Huang 		goto clk_disable;
711ca3de46bSShawn Guo 	}
712ca3de46bSShawn Guo 
713a2291badSTim Harvey 	dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
714a2291badSTim Harvey 		 " critical:%dC passive:%dC\n", data->temp_grade,
71530233a22SDaniel Lezcano 		 data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000,
71630233a22SDaniel Lezcano 		 trips[IMX_TRIP_PASSIVE].temperature / 1000);
717a2291badSTim Harvey 
71837713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
719f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
720f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
72137713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
722f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
723f085f672SAnson Huang 		     measure_freq << data->socdata->measure_freq_shift);
72430233a22SDaniel Lezcano 	imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
7253c94f17eSAnson Huang 
7263c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX)
72730233a22SDaniel Lezcano 		imx_set_panic_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
7283c94f17eSAnson Huang 
729f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
730f085f672SAnson Huang 		     data->socdata->power_down_mask);
731f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
732f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
7334cf2ddf1SOleksij Rempel 	/* After power up, we need a delay before first access can be done. */
7344cf2ddf1SOleksij Rempel 	usleep_range(20, 50);
7354cf2ddf1SOleksij Rempel 
7364cf2ddf1SOleksij Rempel 	/* the core was configured and enabled just before */
7374cf2ddf1SOleksij Rempel 	pm_runtime_set_active(&pdev->dev);
7384cf2ddf1SOleksij Rempel 	pm_runtime_enable(data->dev);
7394cf2ddf1SOleksij Rempel 
7404cf2ddf1SOleksij Rempel 	ret = pm_runtime_resume_and_get(data->dev);
7414cf2ddf1SOleksij Rempel 	if (ret < 0)
7424cf2ddf1SOleksij Rempel 		goto disable_runtime_pm;
74337713a1eSPhilipp Zabel 
744cf1ba1d7SMikhail Lappo 	data->irq_enabled = true;
7457f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_enable(data->tz);
7467f4957beSAndrzej Pietrasiewicz 	if (ret)
7477f4957beSAndrzej Pietrasiewicz 		goto thermal_zone_unregister;
748cf1ba1d7SMikhail Lappo 
74984866ee5SBai Ping 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
75084866ee5SBai Ping 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
75184866ee5SBai Ping 			0, "imx_thermal", data);
75284866ee5SBai Ping 	if (ret < 0) {
75384866ee5SBai Ping 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
754b6ad3981SAnson Huang 		goto thermal_zone_unregister;
75584866ee5SBai Ping 	}
75684866ee5SBai Ping 
7574cf2ddf1SOleksij Rempel 	pm_runtime_put(data->dev);
7584cf2ddf1SOleksij Rempel 
759ca3de46bSShawn Guo 	return 0;
760b6ad3981SAnson Huang 
761b6ad3981SAnson Huang thermal_zone_unregister:
762b6ad3981SAnson Huang 	thermal_zone_device_unregister(data->tz);
7634cf2ddf1SOleksij Rempel disable_runtime_pm:
7644cf2ddf1SOleksij Rempel 	pm_runtime_put_noidle(data->dev);
7654cf2ddf1SOleksij Rempel 	pm_runtime_disable(data->dev);
766b6ad3981SAnson Huang clk_disable:
767b6ad3981SAnson Huang 	clk_disable_unprepare(data->thermal_clk);
768c589c566SAnson Huang legacy_cleanup:
769c589c566SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
770b6ad3981SAnson Huang 
771b6ad3981SAnson Huang 	return ret;
772ca3de46bSShawn Guo }
773ca3de46bSShawn Guo 
imx_thermal_remove(struct platform_device * pdev)774ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
775ca3de46bSShawn Guo {
776ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
77737713a1eSPhilipp Zabel 
7784cf2ddf1SOleksij Rempel 	pm_runtime_put_noidle(data->dev);
7794cf2ddf1SOleksij Rempel 	pm_runtime_disable(data->dev);
780ca3de46bSShawn Guo 
781ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
7829db11010SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
783ca3de46bSShawn Guo 
784ca3de46bSShawn Guo 	return 0;
785ca3de46bSShawn Guo }
786ca3de46bSShawn Guo 
imx_thermal_suspend(struct device * dev)787b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev)
788ca3de46bSShawn Guo {
789ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
7907f4957beSAndrzej Pietrasiewicz 	int ret;
791ca3de46bSShawn Guo 
792ca3de46bSShawn Guo 	/*
793b46cce59SAnson Huang 	 * Need to disable thermal sensor, otherwise, when thermal core
794b46cce59SAnson Huang 	 * try to get temperature before thermal sensor resume, a wrong
795b46cce59SAnson Huang 	 * temperature will be read as the thermal sensor is powered
796f5e50bf4SAndrzej Pietrasiewicz 	 * down. This is done in change_mode() operation called from
7977f4957beSAndrzej Pietrasiewicz 	 * thermal_zone_device_disable()
798ca3de46bSShawn Guo 	 */
7997f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_disable(data->tz);
8007f4957beSAndrzej Pietrasiewicz 	if (ret)
8017f4957beSAndrzej Pietrasiewicz 		return ret;
802ca3de46bSShawn Guo 
8034cf2ddf1SOleksij Rempel 	return pm_runtime_force_suspend(data->dev);
804ca3de46bSShawn Guo }
805ca3de46bSShawn Guo 
imx_thermal_resume(struct device * dev)806b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev)
807ca3de46bSShawn Guo {
808b46cce59SAnson Huang 	struct imx_thermal_data *data = dev_get_drvdata(dev);
809e3bdc8d7SArvind Yadav 	int ret;
810b46cce59SAnson Huang 
8114cf2ddf1SOleksij Rempel 	ret = pm_runtime_force_resume(data->dev);
812e3bdc8d7SArvind Yadav 	if (ret)
813e3bdc8d7SArvind Yadav 		return ret;
814b46cce59SAnson Huang 	/* Enabled thermal sensor after resume */
8154cf2ddf1SOleksij Rempel 	return thermal_zone_device_enable(data->tz);
8164cf2ddf1SOleksij Rempel }
8174cf2ddf1SOleksij Rempel 
imx_thermal_runtime_suspend(struct device * dev)8184cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev)
8194cf2ddf1SOleksij Rempel {
8204cf2ddf1SOleksij Rempel 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8214cf2ddf1SOleksij Rempel 	const struct thermal_soc_data *socdata = data->socdata;
8224cf2ddf1SOleksij Rempel 	struct regmap *map = data->tempmon;
8234cf2ddf1SOleksij Rempel 	int ret;
8244cf2ddf1SOleksij Rempel 
8254cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8264cf2ddf1SOleksij Rempel 			   socdata->measure_temp_mask);
8277f4957beSAndrzej Pietrasiewicz 	if (ret)
8287f4957beSAndrzej Pietrasiewicz 		return ret;
829b46cce59SAnson Huang 
8304cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8314cf2ddf1SOleksij Rempel 			   socdata->power_down_mask);
8324cf2ddf1SOleksij Rempel 	if (ret)
8334cf2ddf1SOleksij Rempel 		return ret;
8344cf2ddf1SOleksij Rempel 
8354cf2ddf1SOleksij Rempel 	clk_disable_unprepare(data->thermal_clk);
8364cf2ddf1SOleksij Rempel 
837ca3de46bSShawn Guo 	return 0;
838ca3de46bSShawn Guo }
839ca3de46bSShawn Guo 
imx_thermal_runtime_resume(struct device * dev)8404cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_resume(struct device *dev)
8414cf2ddf1SOleksij Rempel {
8424cf2ddf1SOleksij Rempel 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8434cf2ddf1SOleksij Rempel 	const struct thermal_soc_data *socdata = data->socdata;
8444cf2ddf1SOleksij Rempel 	struct regmap *map = data->tempmon;
8454cf2ddf1SOleksij Rempel 	int ret;
8464cf2ddf1SOleksij Rempel 
8474cf2ddf1SOleksij Rempel 	ret = clk_prepare_enable(data->thermal_clk);
8484cf2ddf1SOleksij Rempel 	if (ret)
8494cf2ddf1SOleksij Rempel 		return ret;
8504cf2ddf1SOleksij Rempel 
8514cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8524cf2ddf1SOleksij Rempel 			   socdata->power_down_mask);
8534cf2ddf1SOleksij Rempel 	if (ret)
8544cf2ddf1SOleksij Rempel 		return ret;
8554cf2ddf1SOleksij Rempel 
8564cf2ddf1SOleksij Rempel 	ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8574cf2ddf1SOleksij Rempel 			   socdata->measure_temp_mask);
8584cf2ddf1SOleksij Rempel 	if (ret)
8594cf2ddf1SOleksij Rempel 		return ret;
8604cf2ddf1SOleksij Rempel 
8614cf2ddf1SOleksij Rempel 	/*
8624cf2ddf1SOleksij Rempel 	 * According to the temp sensor designers, it may require up to ~17us
8634cf2ddf1SOleksij Rempel 	 * to complete a measurement.
8644cf2ddf1SOleksij Rempel 	 */
8654cf2ddf1SOleksij Rempel 	usleep_range(20, 50);
8664cf2ddf1SOleksij Rempel 
8674cf2ddf1SOleksij Rempel 	return 0;
8684cf2ddf1SOleksij Rempel }
8694cf2ddf1SOleksij Rempel 
8704cf2ddf1SOleksij Rempel static const struct dev_pm_ops imx_thermal_pm_ops = {
8714cf2ddf1SOleksij Rempel 	SET_SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume)
8724cf2ddf1SOleksij Rempel 	SET_RUNTIME_PM_OPS(imx_thermal_runtime_suspend,
8734cf2ddf1SOleksij Rempel 			   imx_thermal_runtime_resume, NULL)
8744cf2ddf1SOleksij Rempel };
875ca3de46bSShawn Guo 
876ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
877ca3de46bSShawn Guo 	.driver = {
878ca3de46bSShawn Guo 		.name	= "imx_thermal",
879ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
880ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
881ca3de46bSShawn Guo 	},
882ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
883ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
884ca3de46bSShawn Guo };
885ca3de46bSShawn Guo module_platform_driver(imx_thermal);
886ca3de46bSShawn Guo 
887ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
888ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
889ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
890ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
891