xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision 5f3c0200)
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 	struct regmap *tempmon;
201ae621557SLeonard Crestez 	u32 c1, c2; /* See formula in imx_init_calib() */
20217e8351aSSascha Hauer 	int temp_passive;
20317e8351aSSascha Hauer 	int temp_critical;
204a2291badSTim Harvey 	int temp_max;
20517e8351aSSascha Hauer 	int alarm_temp;
20617e8351aSSascha Hauer 	int last_temp;
20737713a1eSPhilipp Zabel 	bool irq_enabled;
20837713a1eSPhilipp Zabel 	int irq;
209329fe7b1SAnson Huang 	struct clk *thermal_clk;
2103c94f17eSAnson Huang 	const struct thermal_soc_data *socdata;
211a2291badSTim Harvey 	const char *temp_grade;
212ca3de46bSShawn Guo };
213ca3de46bSShawn Guo 
2143c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data,
21517e8351aSSascha Hauer 			       int panic_temp)
2163c94f17eSAnson Huang {
217f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
2183c94f17eSAnson Huang 	struct regmap *map = data->tempmon;
2193c94f17eSAnson Huang 	int critical_value;
2203c94f17eSAnson Huang 
2213c94f17eSAnson Huang 	critical_value = (data->c2 - panic_temp) / data->c1;
222f085f672SAnson Huang 
223f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR,
224f085f672SAnson Huang 		     soc_data->panic_alarm_mask);
225f085f672SAnson Huang 	regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET,
226f085f672SAnson Huang 		     critical_value << soc_data->panic_alarm_shift);
2273c94f17eSAnson Huang }
2283c94f17eSAnson Huang 
22937713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data,
23017e8351aSSascha Hauer 			       int alarm_temp)
23137713a1eSPhilipp Zabel {
23237713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
233f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
23437713a1eSPhilipp Zabel 	int alarm_value;
23537713a1eSPhilipp Zabel 
23637713a1eSPhilipp Zabel 	data->alarm_temp = alarm_temp;
237f085f672SAnson Huang 
238f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D)
239f085f672SAnson Huang 		alarm_value = alarm_temp / 1000 + data->c1 - 25;
240f085f672SAnson Huang 	else
241749e8be7SAnson Huang 		alarm_value = (data->c2 - alarm_temp) / data->c1;
242f085f672SAnson Huang 
243f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR,
244f085f672SAnson Huang 		     soc_data->high_alarm_mask);
245f085f672SAnson Huang 	regmap_write(map, soc_data->high_alarm_ctrl + REG_SET,
246f085f672SAnson Huang 		     alarm_value << soc_data->high_alarm_shift);
24737713a1eSPhilipp Zabel }
24837713a1eSPhilipp Zabel 
24917e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
250ca3de46bSShawn Guo {
251ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
252f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
253ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
254ca3de46bSShawn Guo 	unsigned int n_meas;
255d92ed2c9SAndrzej Pietrasiewicz 	bool wait, run_measurement;
256ca3de46bSShawn Guo 	u32 val;
257ca3de46bSShawn Guo 
258d92ed2c9SAndrzej Pietrasiewicz 	run_measurement = !data->irq_enabled;
259d92ed2c9SAndrzej Pietrasiewicz 	if (!run_measurement) {
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 
286d92ed2c9SAndrzej Pietrasiewicz 	if (run_measurement) {
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 
334f5e50bf4SAndrzej Pietrasiewicz static int imx_change_mode(struct thermal_zone_device *tz,
335ca3de46bSShawn Guo 			   enum thermal_device_mode mode)
336ca3de46bSShawn Guo {
337ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
33837713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
339f085f672SAnson Huang 	const struct thermal_soc_data *soc_data = data->socdata;
340ca3de46bSShawn Guo 
341ca3de46bSShawn Guo 	if (mode == THERMAL_DEVICE_ENABLED) {
342f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
343f085f672SAnson Huang 			     soc_data->power_down_mask);
344f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
345f085f672SAnson Huang 			     soc_data->measure_temp_mask);
34637713a1eSPhilipp Zabel 
34737713a1eSPhilipp Zabel 		if (!data->irq_enabled) {
34837713a1eSPhilipp Zabel 			data->irq_enabled = true;
34937713a1eSPhilipp Zabel 			enable_irq(data->irq);
35037713a1eSPhilipp Zabel 		}
351ca3de46bSShawn Guo 	} else {
352f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
353f085f672SAnson Huang 			     soc_data->measure_temp_mask);
354f085f672SAnson Huang 		regmap_write(map, soc_data->sensor_ctrl + REG_SET,
355f085f672SAnson Huang 			     soc_data->power_down_mask);
35637713a1eSPhilipp Zabel 
35737713a1eSPhilipp Zabel 		if (data->irq_enabled) {
35837713a1eSPhilipp Zabel 			disable_irq(data->irq);
35937713a1eSPhilipp Zabel 			data->irq_enabled = false;
36037713a1eSPhilipp Zabel 		}
361ca3de46bSShawn Guo 	}
362ca3de46bSShawn Guo 
363ca3de46bSShawn Guo 	return 0;
364ca3de46bSShawn Guo }
365ca3de46bSShawn Guo 
366ca3de46bSShawn Guo static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
367ca3de46bSShawn Guo 			     enum thermal_trip_type *type)
368ca3de46bSShawn Guo {
369ca3de46bSShawn Guo 	*type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
370ca3de46bSShawn Guo 					     THERMAL_TRIP_CRITICAL;
371ca3de46bSShawn Guo 	return 0;
372ca3de46bSShawn Guo }
373ca3de46bSShawn Guo 
37417e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
375ca3de46bSShawn Guo {
376017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
377017e5142SPhilipp Zabel 
378017e5142SPhilipp Zabel 	*temp = data->temp_critical;
379ca3de46bSShawn Guo 	return 0;
380ca3de46bSShawn Guo }
381ca3de46bSShawn Guo 
382ca3de46bSShawn Guo static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
38317e8351aSSascha Hauer 			     int *temp)
384ca3de46bSShawn Guo {
385017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
386017e5142SPhilipp Zabel 
387017e5142SPhilipp Zabel 	*temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive :
388017e5142SPhilipp Zabel 					     data->temp_critical;
389017e5142SPhilipp Zabel 	return 0;
390017e5142SPhilipp Zabel }
391017e5142SPhilipp Zabel 
392017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
39317e8351aSSascha Hauer 			     int temp)
394017e5142SPhilipp Zabel {
395017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
396017e5142SPhilipp Zabel 
397a2291badSTim Harvey 	/* do not allow changing critical threshold */
398017e5142SPhilipp Zabel 	if (trip == IMX_TRIP_CRITICAL)
399017e5142SPhilipp Zabel 		return -EPERM;
400017e5142SPhilipp Zabel 
401a2291badSTim Harvey 	/* do not allow passive to be set higher than critical */
402a2291badSTim Harvey 	if (temp < 0 || temp > data->temp_critical)
403017e5142SPhilipp Zabel 		return -EINVAL;
404017e5142SPhilipp Zabel 
405017e5142SPhilipp Zabel 	data->temp_passive = temp;
406017e5142SPhilipp Zabel 
40737713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
40837713a1eSPhilipp Zabel 
409ca3de46bSShawn Guo 	return 0;
410ca3de46bSShawn Guo }
411ca3de46bSShawn Guo 
412ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
413ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
414ca3de46bSShawn Guo {
415ca3de46bSShawn Guo 	int ret;
416ca3de46bSShawn Guo 
417ca3de46bSShawn Guo 	ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
418ca3de46bSShawn Guo 					       THERMAL_NO_LIMIT,
4196cd9e9f6SKapileshwar Singh 					       THERMAL_NO_LIMIT,
4206cd9e9f6SKapileshwar Singh 					       THERMAL_WEIGHT_DEFAULT);
421ca3de46bSShawn Guo 	if (ret) {
422ca3de46bSShawn Guo 		dev_err(&tz->device,
423ca3de46bSShawn Guo 			"binding zone %s with cdev %s failed:%d\n",
424ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
425ca3de46bSShawn Guo 		return ret;
426ca3de46bSShawn Guo 	}
427ca3de46bSShawn Guo 
428ca3de46bSShawn Guo 	return 0;
429ca3de46bSShawn Guo }
430ca3de46bSShawn Guo 
431ca3de46bSShawn Guo static int imx_unbind(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_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
437ca3de46bSShawn Guo 	if (ret) {
438ca3de46bSShawn Guo 		dev_err(&tz->device,
439ca3de46bSShawn Guo 			"unbinding zone %s with cdev %s failed:%d\n",
440ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
441ca3de46bSShawn Guo 		return ret;
442ca3de46bSShawn Guo 	}
443ca3de46bSShawn Guo 
444ca3de46bSShawn Guo 	return 0;
445ca3de46bSShawn Guo }
446ca3de46bSShawn Guo 
447cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
448ca3de46bSShawn Guo 	.bind = imx_bind,
449ca3de46bSShawn Guo 	.unbind = imx_unbind,
450ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
451f5e50bf4SAndrzej Pietrasiewicz 	.change_mode = imx_change_mode,
452ca3de46bSShawn Guo 	.get_trip_type = imx_get_trip_type,
453ca3de46bSShawn Guo 	.get_trip_temp = imx_get_trip_temp,
454ca3de46bSShawn Guo 	.get_crit_temp = imx_get_crit_temp,
455017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
456ca3de46bSShawn Guo };
457ca3de46bSShawn Guo 
458e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
459ca3de46bSShawn Guo {
460ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
4614e5f61caSUwe Kleine-König 	int n1;
462749e8be7SAnson Huang 	u64 temp64;
463ca3de46bSShawn Guo 
464e4bb2240SUwe Kleine-König 	if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
465ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
466ca3de46bSShawn Guo 		return -EINVAL;
467ca3de46bSShawn Guo 	}
468ca3de46bSShawn Guo 
469ca3de46bSShawn Guo 	/*
470f085f672SAnson Huang 	 * On i.MX7D, we only use the calibration data at 25C to get the temp,
471f085f672SAnson Huang 	 * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
472f085f672SAnson Huang 	 */
473f085f672SAnson Huang 	if (data->socdata->version == TEMPMON_IMX7D) {
474f085f672SAnson Huang 		data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
475f085f672SAnson Huang 		return 0;
476f085f672SAnson Huang 	}
477f085f672SAnson Huang 
478f085f672SAnson Huang 	/*
479c5bbdb4bSUwe Kleine-König 	 * The sensor is calibrated at 25 °C (aka T1) and the value measured
480c5bbdb4bSUwe Kleine-König 	 * (aka N1) at this temperature is provided in bits [31:20] in the
481c5bbdb4bSUwe Kleine-König 	 * i.MX's OCOTP value ANA1.
482c5bbdb4bSUwe Kleine-König 	 * To find the actual temperature T, the following formula has to be used
483c5bbdb4bSUwe Kleine-König 	 * when reading value n from the sensor:
484c5bbdb4bSUwe Kleine-König 	 *
4854e5f61caSUwe Kleine-König 	 * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
4864e5f61caSUwe Kleine-König 	 *   = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
4874e5f61caSUwe Kleine-König 	 *   = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
488c5bbdb4bSUwe Kleine-König 	 *   = c2 - c1 * N
489c5bbdb4bSUwe Kleine-König 	 *
490c5bbdb4bSUwe Kleine-König 	 * with
491c5bbdb4bSUwe Kleine-König 	 *
4924e5f61caSUwe Kleine-König 	 *  T1' = 28.580661 °C
4934e5f61caSUwe Kleine-König 	 *   c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
4944e5f61caSUwe Kleine-König 	 *   c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
4954e5f61caSUwe Kleine-König 	 *      = T1' + N1 * c1
496ca3de46bSShawn Guo 	 */
497e4bb2240SUwe Kleine-König 	n1 = ocotp_ana1 >> 20;
498ca3de46bSShawn Guo 
4994e5f61caSUwe Kleine-König 	temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
500c5bbdb4bSUwe Kleine-König 	temp64 *= 1000; /* to get result in °mC */
5014e5f61caSUwe Kleine-König 	do_div(temp64, 15423 * n1 - 4148468);
502749e8be7SAnson Huang 	data->c1 = temp64;
5034e5f61caSUwe Kleine-König 	data->c2 = n1 * data->c1 + 28581;
504ca3de46bSShawn Guo 
505ae621557SLeonard Crestez 	return 0;
506a2291badSTim Harvey }
507a2291badSTim Harvey 
508e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
509ae621557SLeonard Crestez {
510ae621557SLeonard Crestez 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
511ae621557SLeonard Crestez 
512a2291badSTim Harvey 	/* The maximum die temp is specified by the Temperature Grade */
513e4bb2240SUwe Kleine-König 	switch ((ocotp_mem0 >> 6) & 0x3) {
514339d7492SUwe Kleine-König 	case 0: /* Commercial (0 to 95 °C) */
515a2291badSTim Harvey 		data->temp_grade = "Commercial";
516a2291badSTim Harvey 		data->temp_max = 95000;
517a2291badSTim Harvey 		break;
518339d7492SUwe Kleine-König 	case 1: /* Extended Commercial (-20 °C to 105 °C) */
519a2291badSTim Harvey 		data->temp_grade = "Extended Commercial";
520a2291badSTim Harvey 		data->temp_max = 105000;
521a2291badSTim Harvey 		break;
522339d7492SUwe Kleine-König 	case 2: /* Industrial (-40 °C to 105 °C) */
523a2291badSTim Harvey 		data->temp_grade = "Industrial";
524a2291badSTim Harvey 		data->temp_max = 105000;
525a2291badSTim Harvey 		break;
526339d7492SUwe Kleine-König 	case 3: /* Automotive (-40 °C to 125 °C) */
527a2291badSTim Harvey 		data->temp_grade = "Automotive";
528a2291badSTim Harvey 		data->temp_max = 125000;
529a2291badSTim Harvey 		break;
530a2291badSTim Harvey 	}
531017e5142SPhilipp Zabel 
532017e5142SPhilipp Zabel 	/*
533339d7492SUwe Kleine-König 	 * Set the critical trip point at 5 °C under max
534339d7492SUwe Kleine-König 	 * Set the passive trip point at 10 °C under max (changeable via sysfs)
535017e5142SPhilipp Zabel 	 */
536a2291badSTim Harvey 	data->temp_critical = data->temp_max - (1000 * 5);
537a2291badSTim Harvey 	data->temp_passive = data->temp_max - (1000 * 10);
538ae621557SLeonard Crestez }
539ae621557SLeonard Crestez 
540ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
541ae621557SLeonard Crestez {
542ae621557SLeonard Crestez 	struct regmap *map;
543ae621557SLeonard Crestez 	int ret;
544ae621557SLeonard Crestez 	u32 val;
545ae621557SLeonard Crestez 
546ae621557SLeonard Crestez 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
547ae621557SLeonard Crestez 					      "fsl,tempmon-data");
548ae621557SLeonard Crestez 	if (IS_ERR(map)) {
549ae621557SLeonard Crestez 		ret = PTR_ERR(map);
550ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
551ae621557SLeonard Crestez 		return ret;
552ae621557SLeonard Crestez 	}
553ae621557SLeonard Crestez 
554ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_ANA1, &val);
555ae621557SLeonard Crestez 	if (ret) {
556ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
557ae621557SLeonard Crestez 		return ret;
558ae621557SLeonard Crestez 	}
559ae621557SLeonard Crestez 	ret = imx_init_calib(pdev, val);
560ae621557SLeonard Crestez 	if (ret)
561ae621557SLeonard Crestez 		return ret;
562ae621557SLeonard Crestez 
563ae621557SLeonard Crestez 	ret = regmap_read(map, OCOTP_MEM0, &val);
564ae621557SLeonard Crestez 	if (ret) {
565ae621557SLeonard Crestez 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
566ae621557SLeonard Crestez 		return ret;
567ae621557SLeonard Crestez 	}
568ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
569ae621557SLeonard Crestez 
570ae621557SLeonard Crestez 	return 0;
571ae621557SLeonard Crestez }
572ae621557SLeonard Crestez 
573ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
574ae621557SLeonard Crestez {
575ae621557SLeonard Crestez 	int ret;
576ae621557SLeonard Crestez 	u32 val;
577ae621557SLeonard Crestez 
578ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
579ae621557SLeonard Crestez 	if (ret)
580ae621557SLeonard Crestez 		return ret;
581be926ceeSJean-Christophe Dubois 
582be926ceeSJean-Christophe Dubois 	ret = imx_init_calib(pdev, val);
583be926ceeSJean-Christophe Dubois 	if (ret)
584be926ceeSJean-Christophe Dubois 		return ret;
585ae621557SLeonard Crestez 
586ae621557SLeonard Crestez 	ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
587ae621557SLeonard Crestez 	if (ret)
588ae621557SLeonard Crestez 		return ret;
589ae621557SLeonard Crestez 	imx_init_temp_grade(pdev, val);
590017e5142SPhilipp Zabel 
591ca3de46bSShawn Guo 	return 0;
592ca3de46bSShawn Guo }
593ca3de46bSShawn Guo 
59437713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
59537713a1eSPhilipp Zabel {
59637713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
59737713a1eSPhilipp Zabel 
59837713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
59937713a1eSPhilipp Zabel 	data->irq_enabled = false;
60037713a1eSPhilipp Zabel 
60137713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
60237713a1eSPhilipp Zabel }
60337713a1eSPhilipp Zabel 
60437713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
60537713a1eSPhilipp Zabel {
60637713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
60737713a1eSPhilipp Zabel 
60817e8351aSSascha Hauer 	dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n",
60937713a1eSPhilipp Zabel 		data->alarm_temp / 1000);
61037713a1eSPhilipp Zabel 
6110e70f466SSrinivas Pandruvada 	thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
61237713a1eSPhilipp Zabel 
61337713a1eSPhilipp Zabel 	return IRQ_HANDLED;
61437713a1eSPhilipp Zabel }
61537713a1eSPhilipp Zabel 
6163c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
6173c94f17eSAnson Huang 	{ .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
6183c94f17eSAnson Huang 	{ .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
619f085f672SAnson Huang 	{ .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
6203c94f17eSAnson Huang 	{ /* end */ }
6213c94f17eSAnson Huang };
6223c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
6233c94f17eSAnson Huang 
624c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ
625a1d00154SBastian Stender /*
626a1d00154SBastian Stender  * Create cooling device in case no #cooling-cells property is available in
627a1d00154SBastian Stender  * CPU node
628a1d00154SBastian Stender  */
629a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
630a1d00154SBastian Stender {
631c589c566SAnson Huang 	struct device_node *np;
632b45fd13bSAnson Huang 	int ret = 0;
633a1d00154SBastian Stender 
634c589c566SAnson Huang 	data->policy = cpufreq_cpu_get(0);
635c589c566SAnson Huang 	if (!data->policy) {
636c589c566SAnson Huang 		pr_debug("%s: CPUFreq policy not found\n", __func__);
637c589c566SAnson Huang 		return -EPROBE_DEFER;
638c589c566SAnson Huang 	}
639c589c566SAnson Huang 
640c589c566SAnson Huang 	np = of_get_cpu_node(data->policy->cpu, NULL);
641c589c566SAnson Huang 
642a1d00154SBastian Stender 	if (!np || !of_find_property(np, "#cooling-cells", NULL)) {
643a1d00154SBastian Stender 		data->cdev = cpufreq_cooling_register(data->policy);
644a1d00154SBastian Stender 		if (IS_ERR(data->cdev)) {
645a1d00154SBastian Stender 			ret = PTR_ERR(data->cdev);
646a1d00154SBastian Stender 			cpufreq_cpu_put(data->policy);
647a1d00154SBastian Stender 		}
648a1d00154SBastian Stender 	}
649a1d00154SBastian Stender 
650b45fd13bSAnson Huang 	of_node_put(np);
651b45fd13bSAnson Huang 
652b45fd13bSAnson Huang 	return ret;
653a1d00154SBastian Stender }
654a1d00154SBastian Stender 
655c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
656c589c566SAnson Huang {
657c589c566SAnson Huang 	cpufreq_cooling_unregister(data->cdev);
658c589c566SAnson Huang 	cpufreq_cpu_put(data->policy);
659c589c566SAnson Huang }
660c589c566SAnson Huang 
661c589c566SAnson Huang #else
662c589c566SAnson Huang 
663c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
664c589c566SAnson Huang {
665c589c566SAnson Huang 	return 0;
666c589c566SAnson Huang }
667c589c566SAnson Huang 
668c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
669c589c566SAnson Huang {
670c589c566SAnson Huang }
671c589c566SAnson Huang #endif
672c589c566SAnson Huang 
673ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
674ca3de46bSShawn Guo {
675ca3de46bSShawn Guo 	struct imx_thermal_data *data;
676ca3de46bSShawn Guo 	struct regmap *map;
67737713a1eSPhilipp Zabel 	int measure_freq;
678ca3de46bSShawn Guo 	int ret;
679ca3de46bSShawn Guo 
680ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
681ca3de46bSShawn Guo 	if (!data)
682ca3de46bSShawn Guo 		return -ENOMEM;
683ca3de46bSShawn Guo 
684ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
685ca3de46bSShawn Guo 	if (IS_ERR(map)) {
686ca3de46bSShawn Guo 		ret = PTR_ERR(map);
687ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
688ca3de46bSShawn Guo 		return ret;
689ca3de46bSShawn Guo 	}
690ca3de46bSShawn Guo 	data->tempmon = map;
691ca3de46bSShawn Guo 
692829bc78aSCorentin LABBE 	data->socdata = of_device_get_match_data(&pdev->dev);
6938b051ec3SShailendra Verma 	if (!data->socdata) {
6948b051ec3SShailendra Verma 		dev_err(&pdev->dev, "no device match found\n");
6958b051ec3SShailendra Verma 		return -ENODEV;
6968b051ec3SShailendra Verma 	}
6973c94f17eSAnson Huang 
6983c94f17eSAnson Huang 	/* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
6993c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX) {
700f085f672SAnson Huang 		regmap_write(map, IMX6_MISC1 + REG_CLR,
701f085f672SAnson Huang 			IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
702f085f672SAnson Huang 			| IMX6_MISC1_IRQ_TEMPPANIC);
7033c94f17eSAnson Huang 		/*
7043c94f17eSAnson Huang 		 * reset value of LOW ALARM is incorrect, set it to lowest
7053c94f17eSAnson Huang 		 * value to avoid false trigger of low alarm.
7063c94f17eSAnson Huang 		 */
707f085f672SAnson Huang 		regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
708f085f672SAnson Huang 			     data->socdata->low_alarm_mask);
7093c94f17eSAnson Huang 	}
7103c94f17eSAnson Huang 
71137713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
71237713a1eSPhilipp Zabel 	if (data->irq < 0)
71337713a1eSPhilipp Zabel 		return data->irq;
71437713a1eSPhilipp Zabel 
715ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
716ca3de46bSShawn Guo 
717ae621557SLeonard Crestez 	if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) {
718ae621557SLeonard Crestez 		ret = imx_init_from_nvmem_cells(pdev);
7195f3c0200SAnson Huang 		if (ret)
7205f3c0200SAnson Huang 			return dev_err_probe(&pdev->dev, ret,
7215f3c0200SAnson Huang 					     "failed to init from nvmem\n");
722ae621557SLeonard Crestez 	} else {
723ae621557SLeonard Crestez 		ret = imx_init_from_tempmon_data(pdev);
724ae621557SLeonard Crestez 		if (ret) {
725337a4aecSAnson Huang 			dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
726ae621557SLeonard Crestez 			return ret;
727ae621557SLeonard Crestez 		}
728ca3de46bSShawn Guo 	}
729ca3de46bSShawn Guo 
730ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
731f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
732f085f672SAnson Huang 		     data->socdata->power_down_mask);
733f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
734f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
735f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
736f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
737f085f672SAnson Huang 	if (data->socdata->version != TEMPMON_IMX7D)
738f085f672SAnson Huang 		regmap_write(map, IMX6_MISC0 + REG_SET,
739f085f672SAnson Huang 			IMX6_MISC0_REFTOP_SELBIASOFF);
740f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
741f085f672SAnson Huang 		     data->socdata->power_down_mask);
742ca3de46bSShawn Guo 
743a1d00154SBastian Stender 	ret = imx_thermal_register_legacy_cooling(data);
7445f3c0200SAnson Huang 	if (ret)
7455f3c0200SAnson Huang 		return dev_err_probe(&pdev->dev, ret,
7465f3c0200SAnson Huang 				     "failed to register cpufreq cooling device\n");
747ca3de46bSShawn Guo 
74890a21ff5SHeiner Kallweit 	data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
74990a21ff5SHeiner Kallweit 	if (IS_ERR(data->thermal_clk)) {
75090a21ff5SHeiner Kallweit 		ret = PTR_ERR(data->thermal_clk);
75190a21ff5SHeiner Kallweit 		if (ret != -EPROBE_DEFER)
75290a21ff5SHeiner Kallweit 			dev_err(&pdev->dev,
75390a21ff5SHeiner Kallweit 				"failed to get thermal clk: %d\n", ret);
754c589c566SAnson Huang 		goto legacy_cleanup;
75590a21ff5SHeiner Kallweit 	}
75690a21ff5SHeiner Kallweit 
75790a21ff5SHeiner Kallweit 	/*
75890a21ff5SHeiner Kallweit 	 * Thermal sensor needs clk on to get correct value, normally
75990a21ff5SHeiner Kallweit 	 * we should enable its clk before taking measurement and disable
76090a21ff5SHeiner Kallweit 	 * clk after measurement is done, but if alarm function is enabled,
76190a21ff5SHeiner Kallweit 	 * hardware will auto measure the temperature periodically, so we
76290a21ff5SHeiner Kallweit 	 * need to keep the clk always on for alarm function.
76390a21ff5SHeiner Kallweit 	 */
76490a21ff5SHeiner Kallweit 	ret = clk_prepare_enable(data->thermal_clk);
76590a21ff5SHeiner Kallweit 	if (ret) {
76690a21ff5SHeiner Kallweit 		dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
767c589c566SAnson Huang 		goto legacy_cleanup;
76890a21ff5SHeiner Kallweit 	}
76990a21ff5SHeiner Kallweit 
770ca3de46bSShawn Guo 	data->tz = thermal_zone_device_register("imx_thermal_zone",
771017e5142SPhilipp Zabel 						IMX_TRIP_NUM,
772017e5142SPhilipp Zabel 						BIT(IMX_TRIP_PASSIVE), data,
773ca3de46bSShawn Guo 						&imx_tz_ops, NULL,
774ca3de46bSShawn Guo 						IMX_PASSIVE_DELAY,
775ca3de46bSShawn Guo 						IMX_POLLING_DELAY);
776ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
777ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
778ca3de46bSShawn Guo 		dev_err(&pdev->dev,
779ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
780b6ad3981SAnson Huang 		goto clk_disable;
781ca3de46bSShawn Guo 	}
782ca3de46bSShawn Guo 
783a2291badSTim Harvey 	dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
784a2291badSTim Harvey 		 " critical:%dC passive:%dC\n", data->temp_grade,
785a2291badSTim Harvey 		 data->temp_max / 1000, data->temp_critical / 1000,
786a2291badSTim Harvey 		 data->temp_passive / 1000);
787a2291badSTim Harvey 
78837713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
789f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
790f085f672SAnson Huang 		     data->socdata->measure_freq_mask);
79137713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
792f085f672SAnson Huang 	regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
793f085f672SAnson Huang 		     measure_freq << data->socdata->measure_freq_shift);
79437713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, data->temp_passive);
7953c94f17eSAnson Huang 
7963c94f17eSAnson Huang 	if (data->socdata->version == TEMPMON_IMX6SX)
7973c94f17eSAnson Huang 		imx_set_panic_temp(data, data->temp_critical);
7983c94f17eSAnson Huang 
799f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
800f085f672SAnson Huang 		     data->socdata->power_down_mask);
801f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
802f085f672SAnson Huang 		     data->socdata->measure_temp_mask);
80337713a1eSPhilipp Zabel 
804cf1ba1d7SMikhail Lappo 	data->irq_enabled = true;
8057f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_enable(data->tz);
8067f4957beSAndrzej Pietrasiewicz 	if (ret)
8077f4957beSAndrzej Pietrasiewicz 		goto thermal_zone_unregister;
808cf1ba1d7SMikhail Lappo 
80984866ee5SBai Ping 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
81084866ee5SBai Ping 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
81184866ee5SBai Ping 			0, "imx_thermal", data);
81284866ee5SBai Ping 	if (ret < 0) {
81384866ee5SBai Ping 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
814b6ad3981SAnson Huang 		goto thermal_zone_unregister;
81584866ee5SBai Ping 	}
81684866ee5SBai Ping 
817ca3de46bSShawn Guo 	return 0;
818b6ad3981SAnson Huang 
819b6ad3981SAnson Huang thermal_zone_unregister:
820b6ad3981SAnson Huang 	thermal_zone_device_unregister(data->tz);
821b6ad3981SAnson Huang clk_disable:
822b6ad3981SAnson Huang 	clk_disable_unprepare(data->thermal_clk);
823c589c566SAnson Huang legacy_cleanup:
824c589c566SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
825b6ad3981SAnson Huang 
826b6ad3981SAnson Huang 	return ret;
827ca3de46bSShawn Guo }
828ca3de46bSShawn Guo 
829ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
830ca3de46bSShawn Guo {
831ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
83237713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
83337713a1eSPhilipp Zabel 
83437713a1eSPhilipp Zabel 	/* Disable measurements */
835f085f672SAnson Huang 	regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
836f085f672SAnson Huang 		     data->socdata->power_down_mask);
837329fe7b1SAnson Huang 	if (!IS_ERR(data->thermal_clk))
838329fe7b1SAnson Huang 		clk_disable_unprepare(data->thermal_clk);
839ca3de46bSShawn Guo 
840ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
8419db11010SAnson Huang 	imx_thermal_unregister_legacy_cooling(data);
842ca3de46bSShawn Guo 
843ca3de46bSShawn Guo 	return 0;
844ca3de46bSShawn Guo }
845ca3de46bSShawn Guo 
846b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev)
847ca3de46bSShawn Guo {
848ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
8497f4957beSAndrzej Pietrasiewicz 	int ret;
850ca3de46bSShawn Guo 
851ca3de46bSShawn Guo 	/*
852b46cce59SAnson Huang 	 * Need to disable thermal sensor, otherwise, when thermal core
853b46cce59SAnson Huang 	 * try to get temperature before thermal sensor resume, a wrong
854b46cce59SAnson Huang 	 * temperature will be read as the thermal sensor is powered
855f5e50bf4SAndrzej Pietrasiewicz 	 * down. This is done in change_mode() operation called from
8567f4957beSAndrzej Pietrasiewicz 	 * thermal_zone_device_disable()
857ca3de46bSShawn Guo 	 */
8587f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_disable(data->tz);
8597f4957beSAndrzej Pietrasiewicz 	if (ret)
8607f4957beSAndrzej Pietrasiewicz 		return ret;
861d26eef8bSAnson Huang 	clk_disable_unprepare(data->thermal_clk);
862ca3de46bSShawn Guo 
863ca3de46bSShawn Guo 	return 0;
864ca3de46bSShawn Guo }
865ca3de46bSShawn Guo 
866b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev)
867ca3de46bSShawn Guo {
868b46cce59SAnson Huang 	struct imx_thermal_data *data = dev_get_drvdata(dev);
869e3bdc8d7SArvind Yadav 	int ret;
870b46cce59SAnson Huang 
871e3bdc8d7SArvind Yadav 	ret = clk_prepare_enable(data->thermal_clk);
872e3bdc8d7SArvind Yadav 	if (ret)
873e3bdc8d7SArvind Yadav 		return ret;
874b46cce59SAnson Huang 	/* Enabled thermal sensor after resume */
8757f4957beSAndrzej Pietrasiewicz 	ret = thermal_zone_device_enable(data->tz);
8767f4957beSAndrzej Pietrasiewicz 	if (ret)
8777f4957beSAndrzej Pietrasiewicz 		return ret;
878b46cce59SAnson Huang 
879ca3de46bSShawn Guo 	return 0;
880ca3de46bSShawn Guo }
881ca3de46bSShawn Guo 
882ca3de46bSShawn Guo static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
883ca3de46bSShawn Guo 			 imx_thermal_suspend, imx_thermal_resume);
884ca3de46bSShawn Guo 
885ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
886ca3de46bSShawn Guo 	.driver = {
887ca3de46bSShawn Guo 		.name	= "imx_thermal",
888ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
889ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
890ca3de46bSShawn Guo 	},
891ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
892ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
893ca3de46bSShawn Guo };
894ca3de46bSShawn Guo module_platform_driver(imx_thermal);
895ca3de46bSShawn Guo 
896ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
897ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
898ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
899ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
900