xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision 9db11010)
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>
18ca3de46bSShawn Guo 
19ca3de46bSShawn Guo #define REG_SET		0x4
20ca3de46bSShawn Guo #define REG_CLR		0x8
21ca3de46bSShawn Guo #define REG_TOG		0xc
22ca3de46bSShawn Guo 
23f085f672SAnson Huang /* i.MX6 specific */
24f085f672SAnson Huang #define IMX6_MISC0				0x0150
25f085f672SAnson Huang #define IMX6_MISC0_REFTOP_SELBIASOFF		(1 << 3)
26f085f672SAnson Huang #define IMX6_MISC1				0x0160
27f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPHIGH			(1 << 29)
283c94f17eSAnson Huang /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */
29f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPLOW			(1 << 28)
30f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPPANIC		(1 << 27)
31ca3de46bSShawn Guo 
32f085f672SAnson Huang #define IMX6_TEMPSENSE0				0x0180
33f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT	20
34f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_MASK	(0xfff << 20)
35f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT		8
36f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_MASK		(0xfff << 8)
37f085f672SAnson Huang #define IMX6_TEMPSENSE0_FINISHED		(1 << 2)
38f085f672SAnson Huang #define IMX6_TEMPSENSE0_MEASURE_TEMP		(1 << 1)
39f085f672SAnson Huang #define IMX6_TEMPSENSE0_POWER_DOWN		(1 << 0)
40ca3de46bSShawn Guo 
41f085f672SAnson Huang #define IMX6_TEMPSENSE1				0x0190
42f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ		0xffff
43f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT	0
44ca3de46bSShawn Guo 
45a2291badSTim Harvey #define OCOTP_MEM0			0x0480
46ca3de46bSShawn Guo #define OCOTP_ANA1			0x04e0
47ca3de46bSShawn Guo 
48f085f672SAnson Huang /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */
49f085f672SAnson Huang #define IMX6_TEMPSENSE2				0x0290
50f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT		0
51f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_MASK		0xfff
52f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT	16
53f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_MASK	0xfff0000
54f085f672SAnson Huang 
55f085f672SAnson Huang /* i.MX7 specific */
56f085f672SAnson Huang #define IMX7_ANADIG_DIGPROG			0x800
57f085f672SAnson Huang #define IMX7_TEMPSENSE0				0x300
58f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT	18
59f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_MASK	(0x1ff << 18)
60f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT	9
61f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_MASK		(0x1ff << 9)
62f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT		0
63f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_MASK		0x1ff
64f085f672SAnson Huang 
65f085f672SAnson Huang #define IMX7_TEMPSENSE1				0x310
66f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT	16
67f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK	(0xffff << 16)
68f085f672SAnson Huang #define IMX7_TEMPSENSE1_FINISHED		(1 << 11)
69f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_TEMP		(1 << 10)
70f085f672SAnson Huang #define IMX7_TEMPSENSE1_POWER_DOWN		(1 << 9)
71f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT	0
72f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_MASK		0x1ff
73f085f672SAnson Huang 
74ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */
75ca3de46bSShawn Guo enum imx_thermal_trip {
76ca3de46bSShawn Guo 	IMX_TRIP_PASSIVE,
77ca3de46bSShawn Guo 	IMX_TRIP_CRITICAL,
78ca3de46bSShawn Guo 	IMX_TRIP_NUM,
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 
1173c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6q_data = {
1183c94f17eSAnson Huang 	.version = TEMPMON_IMX6Q,
119f085f672SAnson Huang 
120f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
121f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
122f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
123f085f672SAnson Huang 
124f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
125f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
126f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
127f085f672SAnson Huang 
128f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
129f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
130f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
131f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
132f085f672SAnson Huang 
133f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
134f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
135f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
1363c94f17eSAnson Huang };
1373c94f17eSAnson Huang 
1383c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6sx_data = {
1393c94f17eSAnson Huang 	.version = TEMPMON_IMX6SX,
140f085f672SAnson Huang 
141f085f672SAnson Huang 	.sensor_ctrl = IMX6_TEMPSENSE0,
142f085f672SAnson Huang 	.power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
143f085f672SAnson Huang 	.measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
144f085f672SAnson Huang 
145f085f672SAnson Huang 	.measure_freq_ctrl = IMX6_TEMPSENSE1,
146f085f672SAnson Huang 	.measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
147f085f672SAnson Huang 	.measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
148f085f672SAnson Huang 
149f085f672SAnson Huang 	.temp_data = IMX6_TEMPSENSE0,
150f085f672SAnson Huang 	.temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
151f085f672SAnson Huang 	.temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
152f085f672SAnson Huang 	.temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
153f085f672SAnson Huang 
154f085f672SAnson Huang 	.high_alarm_ctrl = IMX6_TEMPSENSE0,
155f085f672SAnson Huang 	.high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
156f085f672SAnson Huang 	.high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
157f085f672SAnson Huang 
158f085f672SAnson Huang 	.panic_alarm_ctrl = IMX6_TEMPSENSE2,
159f085f672SAnson Huang 	.panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK,
160f085f672SAnson Huang 	.panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT,
161f085f672SAnson Huang 
162f085f672SAnson Huang 	.low_alarm_ctrl = IMX6_TEMPSENSE2,
163f085f672SAnson Huang 	.low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK,
164f085f672SAnson Huang 	.low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT,
165f085f672SAnson Huang };
166f085f672SAnson Huang 
167f085f672SAnson Huang static struct thermal_soc_data thermal_imx7d_data = {
168f085f672SAnson Huang 	.version = TEMPMON_IMX7D,
169f085f672SAnson Huang 
170f085f672SAnson Huang 	.sensor_ctrl = IMX7_TEMPSENSE1,
171f085f672SAnson Huang 	.power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN,
172f085f672SAnson Huang 	.measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP,
173f085f672SAnson Huang 
174f085f672SAnson Huang 	.measure_freq_ctrl = IMX7_TEMPSENSE1,
175f085f672SAnson Huang 	.measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT,
176f085f672SAnson Huang 	.measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK,
177f085f672SAnson Huang 
178f085f672SAnson Huang 	.temp_data = IMX7_TEMPSENSE1,
179f085f672SAnson Huang 	.temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK,
180f085f672SAnson Huang 	.temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT,
181f085f672SAnson Huang 	.temp_valid_mask = IMX7_TEMPSENSE1_FINISHED,
182f085f672SAnson Huang 
183f085f672SAnson Huang 	.panic_alarm_ctrl = IMX7_TEMPSENSE1,
184f085f672SAnson Huang 	.panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK,
185f085f672SAnson Huang 	.panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT,
186f085f672SAnson Huang 
187f085f672SAnson Huang 	.high_alarm_ctrl = IMX7_TEMPSENSE0,
188f085f672SAnson Huang 	.high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK,
189f085f672SAnson Huang 	.high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT,
190f085f672SAnson Huang 
191f085f672SAnson Huang 	.low_alarm_ctrl = IMX7_TEMPSENSE0,
192f085f672SAnson Huang 	.low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK,
193f085f672SAnson Huang 	.low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT,
1943c94f17eSAnson Huang };
1953c94f17eSAnson Huang 
196ca3de46bSShawn Guo struct imx_thermal_data {
1974d753aa7SViresh Kumar 	struct cpufreq_policy *policy;
198ca3de46bSShawn Guo 	struct thermal_zone_device *tz;
199ca3de46bSShawn Guo 	struct thermal_cooling_device *cdev;
200ca3de46bSShawn Guo 	enum thermal_device_mode mode;
201ca3de46bSShawn Guo 	struct regmap *tempmon;
202ae621557SLeonard Crestez 	u32 c1, c2; /* See formula in imx_init_calib() */
20317e8351aSSascha Hauer 	int temp_passive;
20417e8351aSSascha Hauer 	int temp_critical;
205a2291badSTim Harvey 	int temp_max;
20617e8351aSSascha Hauer 	int alarm_temp;
20717e8351aSSascha Hauer 	int last_temp;
20837713a1eSPhilipp Zabel 	bool irq_enabled;
20937713a1eSPhilipp Zabel 	int irq;
210329fe7b1SAnson Huang 	struct clk *thermal_clk;
2113c94f17eSAnson Huang 	const struct thermal_soc_data *socdata;
212a2291badSTim Harvey 	const char *temp_grade;
213ca3de46bSShawn Guo };
214ca3de46bSShawn Guo 
2153c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data,
21617e8351aSSascha Hauer 			       int panic_temp)
2173c94f17eSAnson Huang {
218f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
2193c94f17eSAnson Huang 	struct regmap *map = data->tempmon;
2203c94f17eSAnson Huang 	int critical_value;
2213c94f17eSAnson Huang 
2223c94f17eSAnson Huang 	critical_value = (data->c2 - panic_temp) / data->c1;
223f085f672SAnson Huang 
224f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR,
225f085f672SAnson Huang 		     soc_data->panic_alarm_mask);
226f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET,
227f085f672SAnson Huang 		     critical_value << soc_data->panic_alarm_shift);
2283c94f17eSAnson Huang }
2293c94f17eSAnson Huang 
23037713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data,
23117e8351aSSascha Hauer 			       int alarm_temp)
23237713a1eSPhilipp Zabel {
23337713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
234f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
23537713a1eSPhilipp Zabel 	int alarm_value;
23637713a1eSPhilipp Zabel 
23737713a1eSPhilipp Zabel 	data->alarm_temp = alarm_temp;
238f085f672SAnson Huang 
239f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
240f085f672SAnson Huang 		alarm_value = alarm_temp / 1000 + data->c1 - 25;
241f085f672SAnson Huang 	else
242749e8be7SAnson Huang 		alarm_value = (data->c2 - alarm_temp) / data->c1;
243f085f672SAnson Huang 
244f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR,
245f085f672SAnson Huang 		     soc_data->high_alarm_mask);
246f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_SET,
247f085f672SAnson Huang 		     alarm_value << soc_data->high_alarm_shift);
24837713a1eSPhilipp Zabel }
24937713a1eSPhilipp Zabel 
25017e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
251ca3de46bSShawn Guo {
252ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
253f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
254ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
255ca3de46bSShawn Guo 	unsigned int n_meas;
25637713a1eSPhilipp Zabel 	bool wait;
257ca3de46bSShawn Guo 	u32 val;
258ca3de46bSShawn Guo 
25937713a1eSPhilipp Zabel 	if (data->mode == THERMAL_DEVICE_ENABLED) {
26037713a1eSPhilipp Zabel 		/* Check if a measurement is currently in progress */
261f085f672SAnson Huang 		regmap_read(map, soc_data->temp_data, &val);
262f085f672SAnson Huang 		wait = !(val & soc_data->temp_valid_mask);
26337713a1eSPhilipp Zabel 	} else {
264ca3de46bSShawn Guo 		/*
265ca3de46bSShawn Guo 		 * Every time we measure the temperature, we will power on the
266ca3de46bSShawn Guo 		 * temperature sensor, enable measurements, take a reading,
267ca3de46bSShawn Guo 		 * disable measurements, power off the temperature sensor.
268ca3de46bSShawn Guo 		 */
269f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
270f085f672SAnson Huang 			    soc_data->power_down_mask);
271f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
272f085f672SAnson Huang 			    soc_data->measure_temp_mask);
273ca3de46bSShawn Guo 
27437713a1eSPhilipp Zabel 		wait = true;
27537713a1eSPhilipp Zabel 	}
27637713a1eSPhilipp Zabel 
277ca3de46bSShawn Guo 	/*
278ca3de46bSShawn Guo 	 * According to the temp sensor designers, it may require up to ~17us
279ca3de46bSShawn Guo 	 * to complete a measurement.
280ca3de46bSShawn Guo 	 */
28137713a1eSPhilipp Zabel 	if (wait)
282ca3de46bSShawn Guo 		usleep_range(20, 50);
283ca3de46bSShawn Guo 
284f085f672SAnson Huang 	regmap_read(map, soc_data->temp_data, &val);
28537713a1eSPhilipp Zabel 
28637713a1eSPhilipp Zabel 	if (data->mode != THERMAL_DEVICE_ENABLED) {
287f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
288f085f672SAnson Huang 			     soc_data->measure_temp_mask);
289f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
290f085f672SAnson Huang 			     soc_data->power_down_mask);
29137713a1eSPhilipp Zabel 	}
292ca3de46bSShawn Guo 
293f085f672SAnson Huang 	if ((val & soc_data->temp_valid_mask) == 0) {
294ca3de46bSShawn Guo 		dev_dbg(&tz->device, "temp measurement never finished\n");
295ca3de46bSShawn Guo 		return -EAGAIN;
296ca3de46bSShawn Guo 	}
297ca3de46bSShawn Guo 
298f085f672SAnson Huang 	n_meas = (val & soc_data->temp_value_mask)
299f085f672SAnson Huang 		>> soc_data->temp_value_shift;
300ca3de46bSShawn Guo 
301ae621557SLeonard Crestez 	/* See imx_init_calib() for formula derivation */
302f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
303f085f672SAnson Huang 		*temp = (n_meas - data->c1 + 25) * 1000;
304f085f672SAnson Huang 	else
305749e8be7SAnson Huang 		*temp = data->c2 - n_meas * data->c1;
306ca3de46bSShawn Guo 
3073c94f17eSAnson Huang 	/* Update alarm value to next higher trip point for TEMPMON_IMX6Q */
3083c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6Q) {
3093c94f17eSAnson Huang 		if (data->alarm_temp == data->temp_passive &&
3103c94f17eSAnson Huang 			*temp >= data->temp_passive)
31137713a1eSPhilipp Zabel 			imx_set_alarm_temp(data, data->temp_critical);
3123c94f17eSAnson Huang 		if (data->alarm_temp == data->temp_critical &&
3133c94f17eSAnson Huang 			*temp < data->temp_passive) {
31437713a1eSPhilipp Zabel 			imx_set_alarm_temp(data, data->temp_passive);
31517e8351aSSascha Hauer 			dev_dbg(&tz->device, "thermal alarm off: T < %d\n",
31637713a1eSPhilipp Zabel 				data->alarm_temp / 1000);
31737713a1eSPhilipp Zabel 		}
3183c94f17eSAnson Huang 	}
31937713a1eSPhilipp Zabel 
32037713a1eSPhilipp Zabel 	if (*temp != data->last_temp) {
32117e8351aSSascha Hauer 		dev_dbg(&tz->device, "millicelsius: %d\n", *temp);
32237713a1eSPhilipp Zabel 		data->last_temp = *temp;
32337713a1eSPhilipp Zabel 	}
32437713a1eSPhilipp Zabel 
32537713a1eSPhilipp Zabel 	/* Reenable alarm IRQ if temperature below alarm temperature */
32637713a1eSPhilipp Zabel 	if (!data->irq_enabled && *temp < data->alarm_temp) {
32737713a1eSPhilipp Zabel 		data->irq_enabled = true;
32837713a1eSPhilipp Zabel 		enable_irq(data->irq);
329ca3de46bSShawn Guo 	}
330ca3de46bSShawn Guo 
331ca3de46bSShawn Guo 	return 0;
332ca3de46bSShawn Guo }
333ca3de46bSShawn Guo 
334ca3de46bSShawn Guo static int imx_get_mode(struct thermal_zone_device *tz,
335ca3de46bSShawn Guo 			enum thermal_device_mode *mode)
336ca3de46bSShawn Guo {
337ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
338ca3de46bSShawn Guo 
339ca3de46bSShawn Guo 	*mode = data->mode;
340ca3de46bSShawn Guo 
341ca3de46bSShawn Guo 	return 0;
342ca3de46bSShawn Guo }
343ca3de46bSShawn Guo 
344ca3de46bSShawn Guo static int imx_set_mode(struct thermal_zone_device *tz,
345ca3de46bSShawn Guo 			enum thermal_device_mode mode)
346ca3de46bSShawn Guo {
347ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
34837713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
349f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
350ca3de46bSShawn Guo 
351ca3de46bSShawn Guo 	if (mode == THERMAL_DEVICE_ENABLED) {
352ca3de46bSShawn Guo 		tz->polling_delay = IMX_POLLING_DELAY;
353ca3de46bSShawn Guo 		tz->passive_delay = IMX_PASSIVE_DELAY;
35437713a1eSPhilipp Zabel 
355f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
356f085f672SAnson Huang 			     soc_data->power_down_mask);
357f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
358f085f672SAnson Huang 			     soc_data->measure_temp_mask);
35937713a1eSPhilipp Zabel 
36037713a1eSPhilipp Zabel 		if (!data->irq_enabled) {
36137713a1eSPhilipp Zabel 			data->irq_enabled = true;
36237713a1eSPhilipp Zabel 			enable_irq(data->irq);
36337713a1eSPhilipp Zabel 		}
364ca3de46bSShawn Guo 	} else {
365f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
366f085f672SAnson Huang 			     soc_data->measure_temp_mask);
367f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
368f085f672SAnson Huang 			     soc_data->power_down_mask);
36937713a1eSPhilipp Zabel 
370ca3de46bSShawn Guo 		tz->polling_delay = 0;
371ca3de46bSShawn Guo 		tz->passive_delay = 0;
37237713a1eSPhilipp Zabel 
37337713a1eSPhilipp Zabel 		if (data->irq_enabled) {
37437713a1eSPhilipp Zabel 			disable_irq(data->irq);
37537713a1eSPhilipp Zabel 			data->irq_enabled = false;
37637713a1eSPhilipp Zabel 		}
377ca3de46bSShawn Guo 	}
378ca3de46bSShawn Guo 
379ca3de46bSShawn Guo 	data->mode = mode;
3800e70f466SSrinivas Pandruvada 	thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
381ca3de46bSShawn Guo 
382ca3de46bSShawn Guo 	return 0;
383ca3de46bSShawn Guo }
384ca3de46bSShawn Guo 
385ca3de46bSShawn Guo static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
386ca3de46bSShawn Guo 			     enum thermal_trip_type *type)
387ca3de46bSShawn Guo {
388ca3de46bSShawn Guo 	*type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
389ca3de46bSShawn Guo 					     THERMAL_TRIP_CRITICAL;
390ca3de46bSShawn Guo 	return 0;
391ca3de46bSShawn Guo }
392ca3de46bSShawn Guo 
39317e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
394ca3de46bSShawn Guo {
395017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
396017e5142SPhilipp Zabel 
397017e5142SPhilipp Zabel 	*temp = data->temp_critical;
398ca3de46bSShawn Guo 	return 0;
399ca3de46bSShawn Guo }
400ca3de46bSShawn Guo 
401ca3de46bSShawn Guo static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
40217e8351aSSascha Hauer 			     int *temp)
403ca3de46bSShawn Guo {
404017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
405017e5142SPhilipp Zabel 
406017e5142SPhilipp Zabel 	*temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive :
407017e5142SPhilipp Zabel 					     data->temp_critical;
408017e5142SPhilipp Zabel 	return 0;
409017e5142SPhilipp Zabel }
410017e5142SPhilipp Zabel 
411017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
41217e8351aSSascha Hauer 			     int temp)
413017e5142SPhilipp Zabel {
414017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
415017e5142SPhilipp Zabel 
416a2291badSTim Harvey 	/* do not allow changing critical threshold */
417017e5142SPhilipp Zabel 	if (trip == IMX_TRIP_CRITICAL)
418017e5142SPhilipp Zabel 		return -EPERM;
419017e5142SPhilipp Zabel 
420a2291badSTim Harvey 	/* do not allow passive to be set higher than critical */
421a2291badSTim Harvey 	if (temp < 0 || temp > data->temp_critical)
422017e5142SPhilipp Zabel 		return -EINVAL;
423017e5142SPhilipp Zabel 
424017e5142SPhilipp Zabel 	data->temp_passive = temp;
425017e5142SPhilipp Zabel 
42637713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
42737713a1eSPhilipp Zabel 
428ca3de46bSShawn Guo 	return 0;
429ca3de46bSShawn Guo }
430ca3de46bSShawn Guo 
431ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
432ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
433ca3de46bSShawn Guo {
434ca3de46bSShawn Guo 	int ret;
435ca3de46bSShawn Guo 
436ca3de46bSShawn Guo 	ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
437ca3de46bSShawn Guo 					       THERMAL_NO_LIMIT,
4386cd9e9f6SKapileshwar Singh 					       THERMAL_NO_LIMIT,
4396cd9e9f6SKapileshwar Singh 					       THERMAL_WEIGHT_DEFAULT);
440ca3de46bSShawn Guo 	if (ret) {
441ca3de46bSShawn Guo 		dev_err(&tz->device,
442ca3de46bSShawn Guo 			"binding zone %s with cdev %s failed:%d\n",
443ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
444ca3de46bSShawn Guo 		return ret;
445ca3de46bSShawn Guo 	}
446ca3de46bSShawn Guo 
447ca3de46bSShawn Guo 	return 0;
448ca3de46bSShawn Guo }
449ca3de46bSShawn Guo 
450ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
451ca3de46bSShawn Guo 		      struct thermal_cooling_device *cdev)
452ca3de46bSShawn Guo {
453ca3de46bSShawn Guo 	int ret;
454ca3de46bSShawn Guo 
455ca3de46bSShawn Guo 	ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
456ca3de46bSShawn Guo 	if (ret) {
457ca3de46bSShawn Guo 		dev_err(&tz->device,
458ca3de46bSShawn Guo 			"unbinding zone %s with cdev %s failed:%d\n",
459ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
460ca3de46bSShawn Guo 		return ret;
461ca3de46bSShawn Guo 	}
462ca3de46bSShawn Guo 
463ca3de46bSShawn Guo 	return 0;
464ca3de46bSShawn Guo }
465ca3de46bSShawn Guo 
466cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
467ca3de46bSShawn Guo 	.bind = imx_bind,
468ca3de46bSShawn Guo 	.unbind = imx_unbind,
469ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
470ca3de46bSShawn Guo 	.get_mode = imx_get_mode,
471ca3de46bSShawn Guo 	.set_mode = imx_set_mode,
472ca3de46bSShawn Guo 	.get_trip_type = imx_get_trip_type,
473ca3de46bSShawn Guo 	.get_trip_temp = imx_get_trip_temp,
474ca3de46bSShawn Guo 	.get_crit_temp = imx_get_crit_temp,
475017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
476ca3de46bSShawn Guo };
477ca3de46bSShawn Guo 
478e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
479ca3de46bSShawn Guo {
480ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
4814e5f61caSUwe Kleine-König 	int n1;
482749e8be7SAnson Huang 	u64 temp64;
483ca3de46bSShawn Guo 
484e4bb2240SUwe Kleine-König 	if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
485ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
486ca3de46bSShawn Guo 		return -EINVAL;
487ca3de46bSShawn Guo 	}
488ca3de46bSShawn Guo 
489ca3de46bSShawn Guo 	/*
490f085f672SAnson Huang 	 * On i.MX7D, we only use the calibration data at 25C to get the temp,
491f085f672SAnson Huang 	 * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
492f085f672SAnson Huang 	 */
493f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D) {
494f085f672SAnson Huang 		data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
495f085f672SAnson Huang 		return 0;
496f085f672SAnson Huang 	}
497f085f672SAnson Huang 
498f085f672SAnson Huang 	/*
499c5bbdb4bSUwe Kleine-König 	 * The sensor is calibrated at 25 °C (aka T1) and the value measured
500c5bbdb4bSUwe Kleine-König 	 * (aka N1) at this temperature is provided in bits [31:20] in the
501c5bbdb4bSUwe Kleine-König 	 * i.MX's OCOTP value ANA1.
502c5bbdb4bSUwe Kleine-König 	 * To find the actual temperature T, the following formula has to be used
503c5bbdb4bSUwe Kleine-König 	 * when reading value n from the sensor:
504c5bbdb4bSUwe Kleine-König 	 *
5054e5f61caSUwe Kleine-König 	 * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
5064e5f61caSUwe Kleine-König 	 *   = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
5074e5f61caSUwe Kleine-König 	 *   = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
508c5bbdb4bSUwe Kleine-König 	 *   = c2 - c1 * N
509c5bbdb4bSUwe Kleine-König 	 *
510c5bbdb4bSUwe Kleine-König 	 * with
511c5bbdb4bSUwe Kleine-König 	 *
5124e5f61caSUwe Kleine-König 	 *  T1' = 28.580661 °C
5134e5f61caSUwe Kleine-König 	 *   c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
5144e5f61caSUwe Kleine-König 	 *   c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
5154e5f61caSUwe Kleine-König 	 *      = T1' + N1 * c1
516ca3de46bSShawn Guo 	 */
517e4bb2240SUwe Kleine-König 	n1 = ocotp_ana1 >> 20;
518ca3de46bSShawn Guo 
5194e5f61caSUwe Kleine-König 	temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
520c5bbdb4bSUwe Kleine-König 	temp64 *= 1000; /* to get result in °mC */
5214e5f61caSUwe Kleine-König 	do_div(temp64, 15423 * n1 - 4148468);
522749e8be7SAnson Huang 	data->c1 = temp64;
5234e5f61caSUwe Kleine-König 	data->c2 = n1 * data->c1 + 28581;
524ca3de46bSShawn Guo 
525ae621557SLeonard Crestez 	return 0;
526a2291badSTim Harvey }
527a2291badSTim Harvey 
528e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
529ae621557SLeonard Crestez {
530ae621557SLeonard Crestez 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
531ae621557SLeonard Crestez 
532a2291badSTim Harvey 	/* The maximum die temp is specified by the Temperature Grade */
533e4bb2240SUwe Kleine-König 	switch ((ocotp_mem0 >> 6) & 0x3) {
534339d7492SUwe Kleine-König 	case 0: /* Commercial (0 to 95 °C) */
535a2291badSTim Harvey 		data->temp_grade = "Commercial";
536a2291badSTim Harvey 		data->temp_max = 95000;
537a2291badSTim Harvey 		break;
538339d7492SUwe Kleine-König 	case 1: /* Extended Commercial (-20 °C to 105 °C) */
539a2291badSTim Harvey 		data->temp_grade = "Extended Commercial";
540a2291badSTim Harvey 		data->temp_max = 105000;
541a2291badSTim Harvey 		break;
542339d7492SUwe Kleine-König 	case 2: /* Industrial (-40 °C to 105 °C) */
543a2291badSTim Harvey 		data->temp_grade = "Industrial";
544a2291badSTim Harvey 		data->temp_max = 105000;
545a2291badSTim Harvey 		break;
546339d7492SUwe Kleine-König 	case 3: /* Automotive (-40 °C to 125 °C) */
547a2291badSTim Harvey 		data->temp_grade = "Automotive";
548a2291badSTim Harvey 		data->temp_max = 125000;
549a2291badSTim Harvey 		break;
550a2291badSTim Harvey 	}
551017e5142SPhilipp Zabel 
552017e5142SPhilipp Zabel 	/*
553339d7492SUwe Kleine-König 	 * Set the critical trip point at 5 °C under max
554339d7492SUwe Kleine-König 	 * Set the passive trip point at 10 °C under max (changeable via sysfs)
555017e5142SPhilipp Zabel 	 */
556a2291badSTim Harvey 	data->temp_critical = data->temp_max - (1000 * 5);
557a2291badSTim Harvey 	data->temp_passive = data->temp_max - (1000 * 10);
558ae621557SLeonard Crestez }
559ae621557SLeonard Crestez 
560ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
561ae621557SLeonard Crestez {
562ae621557SLeonard Crestez 	struct regmap *map;
563ae621557SLeonard Crestez 	int ret;
564ae621557SLeonard Crestez 	u32 val;
565ae621557SLeonard Crestez 
566ae621557SLeonard Crestez 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
567ae621557SLeonard Crestez 					      "fsl,tempmon-data");
568ae621557SLeonard Crestez 	if (IS_ERR(map)) {
569ae621557SLeonard Crestez 		ret = PTR_ERR(map);
570ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
571ae621557SLeonard Crestez 		return ret;
572ae621557SLeonard Crestez 	}
573ae621557SLeonard Crestez 
574ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_ANA1, &val);
575ae621557SLeonard Crestez 	if (ret) {
576ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
577ae621557SLeonard Crestez 		return ret;
578ae621557SLeonard Crestez 	}
579ae621557SLeonard Crestez 	ret = imx_init_calib(pdev, val);
580ae621557SLeonard Crestez 	if (ret)
581ae621557SLeonard Crestez 		return ret;
582ae621557SLeonard Crestez 
583ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_MEM0, &val);
584ae621557SLeonard Crestez 	if (ret) {
585ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
586ae621557SLeonard Crestez 		return ret;
587ae621557SLeonard Crestez 	}
588ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
589ae621557SLeonard Crestez 
590ae621557SLeonard Crestez 	return 0;
591ae621557SLeonard Crestez }
592ae621557SLeonard Crestez 
593ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
594ae621557SLeonard Crestez {
595ae621557SLeonard Crestez 	int ret;
596ae621557SLeonard Crestez 	u32 val;
597ae621557SLeonard Crestez 
598ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
599ae621557SLeonard Crestez 	if (ret)
600ae621557SLeonard Crestez 		return ret;
601be926ceeSJean-Christophe Dubois 
602be926ceeSJean-Christophe Dubois 	ret = imx_init_calib(pdev, val);
603be926ceeSJean-Christophe Dubois 	if (ret)
604be926ceeSJean-Christophe Dubois 		return ret;
605ae621557SLeonard Crestez 
606ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
607ae621557SLeonard Crestez 	if (ret)
608ae621557SLeonard Crestez 		return ret;
609ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
610017e5142SPhilipp Zabel 
611ca3de46bSShawn Guo 	return 0;
612ca3de46bSShawn Guo }
613ca3de46bSShawn Guo 
61437713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
61537713a1eSPhilipp Zabel {
61637713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
61737713a1eSPhilipp Zabel 
61837713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
61937713a1eSPhilipp Zabel 	data->irq_enabled = false;
62037713a1eSPhilipp Zabel 
62137713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
62237713a1eSPhilipp Zabel }
62337713a1eSPhilipp Zabel 
62437713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
62537713a1eSPhilipp Zabel {
62637713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
62737713a1eSPhilipp Zabel 
62817e8351aSSascha Hauer 	dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n",
62937713a1eSPhilipp Zabel 		data->alarm_temp / 1000);
63037713a1eSPhilipp Zabel 
6310e70f466SSrinivas Pandruvada 	thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
63237713a1eSPhilipp Zabel 
63337713a1eSPhilipp Zabel 	return IRQ_HANDLED;
63437713a1eSPhilipp Zabel }
63537713a1eSPhilipp Zabel 
6363c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
6373c94f17eSAnson Huang 	{ .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
6383c94f17eSAnson Huang 	{ .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
639f085f672SAnson Huang 	{ .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
6403c94f17eSAnson Huang 	{ /* end */ }
6413c94f17eSAnson Huang };
6423c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
6433c94f17eSAnson Huang 
644c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ
645a1d00154SBastian Stender /*
646a1d00154SBastian Stender  * Create cooling device in case no #cooling-cells property is available in
647a1d00154SBastian Stender  * CPU node
648a1d00154SBastian Stender  */
649a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
650a1d00154SBastian Stender {
651c589c566SAnson Huang 	struct device_node *np;
652a1d00154SBastian Stender 	int ret;
653a1d00154SBastian Stender 
654c589c566SAnson Huang 	data->policy = cpufreq_cpu_get(0);
655c589c566SAnson Huang 	if (!data->policy) {
656c589c566SAnson Huang 		pr_debug("%s: CPUFreq policy not found\n", __func__);
657c589c566SAnson Huang 		return -EPROBE_DEFER;
658c589c566SAnson Huang 	}
659c589c566SAnson Huang 
660c589c566SAnson Huang 	np = of_get_cpu_node(data->policy->cpu, NULL);
661c589c566SAnson Huang 
662a1d00154SBastian Stender 	if (!np || !of_find_property(np, "#cooling-cells", NULL)) {
663a1d00154SBastian Stender 		data->cdev = cpufreq_cooling_register(data->policy);
664a1d00154SBastian Stender 		if (IS_ERR(data->cdev)) {
665a1d00154SBastian Stender 			ret = PTR_ERR(data->cdev);
666a1d00154SBastian Stender 			cpufreq_cpu_put(data->policy);
667a1d00154SBastian Stender 			return ret;
668a1d00154SBastian Stender 		}
669a1d00154SBastian Stender 	}
670a1d00154SBastian Stender 
671a1d00154SBastian Stender 	return 0;
672a1d00154SBastian Stender }
673a1d00154SBastian Stender 
674c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
675c589c566SAnson Huang {
676c589c566SAnson Huang 	cpufreq_cooling_unregister(data->cdev);
677c589c566SAnson Huang 	cpufreq_cpu_put(data->policy);
678c589c566SAnson Huang }
679c589c566SAnson Huang 
680c589c566SAnson Huang #else
681c589c566SAnson Huang 
682c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
683c589c566SAnson Huang {
684c589c566SAnson Huang 	return 0;
685c589c566SAnson Huang }
686c589c566SAnson Huang 
687c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
688c589c566SAnson Huang {
689c589c566SAnson Huang }
690c589c566SAnson Huang #endif
691c589c566SAnson Huang 
692ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
693ca3de46bSShawn Guo {
694ca3de46bSShawn Guo 	struct imx_thermal_data *data;
695ca3de46bSShawn Guo 	struct regmap *map;
69637713a1eSPhilipp Zabel 	int measure_freq;
697ca3de46bSShawn Guo 	int ret;
698ca3de46bSShawn Guo 
699ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
700ca3de46bSShawn Guo 	if (!data)
701ca3de46bSShawn Guo 		return -ENOMEM;
702ca3de46bSShawn Guo 
703ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
704ca3de46bSShawn Guo 	if (IS_ERR(map)) {
705ca3de46bSShawn Guo 		ret = PTR_ERR(map);
706ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
707ca3de46bSShawn Guo 		return ret;
708ca3de46bSShawn Guo 	}
709ca3de46bSShawn Guo 	data->tempmon = map;
710ca3de46bSShawn Guo 
711829bc78aSCorentin LABBE 	data->socdata = of_device_get_match_data(&pdev->dev);
7128b051ec3SShailendra Verma 	if (!data->socdata) {
7138b051ec3SShailendra Verma 		dev_err(&pdev->dev, "no device match found\n");
7148b051ec3SShailendra Verma 		return -ENODEV;
7158b051ec3SShailendra Verma 	}
7163c94f17eSAnson Huang 
7173c94f17eSAnson Huang 	/* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
7183c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX) {
719f085f672SAnson Huang 		regmap_write(map, IMX6_MISC1 + REG_CLR,
720f085f672SAnson Huang 			IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
721f085f672SAnson Huang 			| IMX6_MISC1_IRQ_TEMPPANIC);
7223c94f17eSAnson Huang 		/*
7233c94f17eSAnson Huang 		 * reset value of LOW ALARM is incorrect, set it to lowest
7243c94f17eSAnson Huang 		 * value to avoid false trigger of low alarm.
7253c94f17eSAnson Huang 		 */
726f085f672SAnson Huang 		regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
727f085f672SAnson Huang 			     data->socdata->low_alarm_mask);
7283c94f17eSAnson Huang 	}
7293c94f17eSAnson Huang 
73037713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
73137713a1eSPhilipp Zabel 	if (data->irq < 0)
73237713a1eSPhilipp Zabel 		return data->irq;
73337713a1eSPhilipp Zabel 
734ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
735ca3de46bSShawn Guo 
736ae621557SLeonard Crestez 	if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) {
737ae621557SLeonard Crestez 		ret = imx_init_from_nvmem_cells(pdev);
738925b3836SAnson Huang 		if (ret) {
739ae621557SLeonard Crestez 			if (ret == -EPROBE_DEFER)
740ca3de46bSShawn Guo 				return ret;
741925b3836SAnson Huang 
742ae621557SLeonard Crestez 			dev_err(&pdev->dev, "failed to init from nvmem: %d\n",
743ae621557SLeonard Crestez 				ret);
744ae621557SLeonard Crestez 			return ret;
745ae621557SLeonard Crestez 		}
746ae621557SLeonard Crestez 	} else {
747ae621557SLeonard Crestez 		ret = imx_init_from_tempmon_data(pdev);
748ae621557SLeonard Crestez 		if (ret) {
749337a4aecSAnson Huang 			dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
750ae621557SLeonard Crestez 			return ret;
751ae621557SLeonard Crestez 		}
752ca3de46bSShawn Guo 	}
753ca3de46bSShawn Guo 
754ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
755f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
756f085f672SAnson Huang 		     data->socdata->power_down_mask);
757f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
758f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
759f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
760f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
761f085f672SAnson Huang 	if (data->socdata->version != TEMPMON_IMX7D)
762f085f672SAnson Huang 		regmap_write(map, IMX6_MISC0 + REG_SET,
763f085f672SAnson Huang 			IMX6_MISC0_REFTOP_SELBIASOFF);
764f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
765f085f672SAnson Huang 		     data->socdata->power_down_mask);
766ca3de46bSShawn Guo 
767a1d00154SBastian Stender 	ret = imx_thermal_register_legacy_cooling(data);
768a1d00154SBastian Stender 	if (ret) {
769c589c566SAnson Huang 		if (ret == -EPROBE_DEFER)
770c589c566SAnson Huang 			return ret;
771c589c566SAnson Huang 
772ca3de46bSShawn Guo 		dev_err(&pdev->dev,
7734d753aa7SViresh Kumar 			"failed to register cpufreq cooling device: %d\n", ret);
774ca3de46bSShawn Guo 		return ret;
775ca3de46bSShawn Guo 	}
776ca3de46bSShawn Guo 
77790a21ff5SHeiner Kallweit 	data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
77890a21ff5SHeiner Kallweit 	if (IS_ERR(data->thermal_clk)) {
77990a21ff5SHeiner Kallweit 		ret = PTR_ERR(data->thermal_clk);
78090a21ff5SHeiner Kallweit 		if (ret != -EPROBE_DEFER)
78190a21ff5SHeiner Kallweit 			dev_err(&pdev->dev,
78290a21ff5SHeiner Kallweit 				"failed to get thermal clk: %d\n", ret);
783c589c566SAnson Huang 		goto legacy_cleanup;
78490a21ff5SHeiner Kallweit 	}
78590a21ff5SHeiner Kallweit 
78690a21ff5SHeiner Kallweit 	/*
78790a21ff5SHeiner Kallweit 	 * Thermal sensor needs clk on to get correct value, normally
78890a21ff5SHeiner Kallweit 	 * we should enable its clk before taking measurement and disable
78990a21ff5SHeiner Kallweit 	 * clk after measurement is done, but if alarm function is enabled,
79090a21ff5SHeiner Kallweit 	 * hardware will auto measure the temperature periodically, so we
79190a21ff5SHeiner Kallweit 	 * need to keep the clk always on for alarm function.
79290a21ff5SHeiner Kallweit 	 */
79390a21ff5SHeiner Kallweit 	ret = clk_prepare_enable(data->thermal_clk);
79490a21ff5SHeiner Kallweit 	if (ret) {
79590a21ff5SHeiner Kallweit 		dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
796c589c566SAnson Huang 		goto legacy_cleanup;
79790a21ff5SHeiner Kallweit 	}
79890a21ff5SHeiner Kallweit 
799ca3de46bSShawn Guo 	data->tz = thermal_zone_device_register("imx_thermal_zone",
800017e5142SPhilipp Zabel 						IMX_TRIP_NUM,
801017e5142SPhilipp Zabel 						BIT(IMX_TRIP_PASSIVE), data,
802ca3de46bSShawn Guo 						&imx_tz_ops, NULL,
803ca3de46bSShawn Guo 						IMX_PASSIVE_DELAY,
804ca3de46bSShawn Guo 						IMX_POLLING_DELAY);
805ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
806ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
807ca3de46bSShawn Guo 		dev_err(&pdev->dev,
808ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
809b6ad3981SAnson Huang 		goto clk_disable;
810ca3de46bSShawn Guo 	}
811ca3de46bSShawn Guo 
812a2291badSTim Harvey 	dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
813a2291badSTim Harvey 		 " critical:%dC passive:%dC\n", data->temp_grade,
814a2291badSTim Harvey 		 data->temp_max / 1000, data->temp_critical / 1000,
815a2291badSTim Harvey 		 data->temp_passive / 1000);
816a2291badSTim Harvey 
81737713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
818f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
819f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
82037713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
821f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
822f085f672SAnson Huang 		     measure_freq << data->socdata->measure_freq_shift);
82337713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, data->temp_passive);
8243c94f17eSAnson Huang 
8253c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX)
8263c94f17eSAnson Huang 		imx_set_panic_temp(data, data->temp_critical);
8273c94f17eSAnson Huang 
828f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
829f085f672SAnson Huang 		     data->socdata->power_down_mask);
830f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
831f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
83237713a1eSPhilipp Zabel 
833cf1ba1d7SMikhail Lappo 	data->irq_enabled = true;
834cf1ba1d7SMikhail Lappo 	data->mode = THERMAL_DEVICE_ENABLED;
835cf1ba1d7SMikhail Lappo 
83684866ee5SBai Ping 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
83784866ee5SBai Ping 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
83884866ee5SBai Ping 			0, "imx_thermal", data);
83984866ee5SBai Ping 	if (ret < 0) {
84084866ee5SBai Ping 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
841b6ad3981SAnson Huang 		goto thermal_zone_unregister;
84284866ee5SBai Ping 	}
84384866ee5SBai Ping 
844ca3de46bSShawn Guo 	return 0;
845b6ad3981SAnson Huang 
846b6ad3981SAnson Huang thermal_zone_unregister:
847b6ad3981SAnson Huang 	thermal_zone_device_unregister(data->tz);
848b6ad3981SAnson Huang clk_disable:
849b6ad3981SAnson Huang 	clk_disable_unprepare(data->thermal_clk);
850c589c566SAnson Huang legacy_cleanup:
851c589c566SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
852b6ad3981SAnson Huang 
853b6ad3981SAnson Huang 	return ret;
854ca3de46bSShawn Guo }
855ca3de46bSShawn Guo 
856ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
857ca3de46bSShawn Guo {
858ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
85937713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
86037713a1eSPhilipp Zabel 
86137713a1eSPhilipp Zabel 	/* Disable measurements */
862f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
863f085f672SAnson Huang 		     data->socdata->power_down_mask);
864329fe7b1SAnson Huang 	if (!IS_ERR(data->thermal_clk))
865329fe7b1SAnson Huang 		clk_disable_unprepare(data->thermal_clk);
866ca3de46bSShawn Guo 
867ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
8689db11010SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
869ca3de46bSShawn Guo 
870ca3de46bSShawn Guo 	return 0;
871ca3de46bSShawn Guo }
872ca3de46bSShawn Guo 
873b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev)
874ca3de46bSShawn Guo {
875ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
876ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
877ca3de46bSShawn Guo 
878ca3de46bSShawn Guo 	/*
879b46cce59SAnson Huang 	 * Need to disable thermal sensor, otherwise, when thermal core
880b46cce59SAnson Huang 	 * try to get temperature before thermal sensor resume, a wrong
881b46cce59SAnson Huang 	 * temperature will be read as the thermal sensor is powered
882b46cce59SAnson Huang 	 * down.
883ca3de46bSShawn Guo 	 */
884f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
885f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
886f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
887f085f672SAnson Huang 		     data->socdata->power_down_mask);
888b46cce59SAnson Huang 	data->mode = THERMAL_DEVICE_DISABLED;
889d26eef8bSAnson Huang 	clk_disable_unprepare(data->thermal_clk);
890ca3de46bSShawn Guo 
891ca3de46bSShawn Guo 	return 0;
892ca3de46bSShawn Guo }
893ca3de46bSShawn Guo 
894b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev)
895ca3de46bSShawn Guo {
896b46cce59SAnson Huang 	struct imx_thermal_data *data = dev_get_drvdata(dev);
897b46cce59SAnson Huang 	struct regmap *map = data->tempmon;
898e3bdc8d7SArvind Yadav 	int ret;
899b46cce59SAnson Huang 
900e3bdc8d7SArvind Yadav 	ret = clk_prepare_enable(data->thermal_clk);
901e3bdc8d7SArvind Yadav 	if (ret)
902e3bdc8d7SArvind Yadav 		return ret;
903b46cce59SAnson Huang 	/* Enabled thermal sensor after resume */
904f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
905f085f672SAnson Huang 		     data->socdata->power_down_mask);
906f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
907f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
908b46cce59SAnson Huang 	data->mode = THERMAL_DEVICE_ENABLED;
909b46cce59SAnson Huang 
910ca3de46bSShawn Guo 	return 0;
911ca3de46bSShawn Guo }
912ca3de46bSShawn Guo 
913ca3de46bSShawn Guo static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
914ca3de46bSShawn Guo 			 imx_thermal_suspend, imx_thermal_resume);
915ca3de46bSShawn Guo 
916ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
917ca3de46bSShawn Guo 	.driver = {
918ca3de46bSShawn Guo 		.name	= "imx_thermal",
919ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
920ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
921ca3de46bSShawn Guo 	},
922ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
923ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
924ca3de46bSShawn Guo };
925ca3de46bSShawn Guo module_platform_driver(imx_thermal);
926ca3de46bSShawn Guo 
927ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
928ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
929ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
930ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
931