xref: /openbmc/linux/drivers/thermal/imx_thermal.c (revision cbb07bb3)
1ca3de46bSShawn Guo /*
2ca3de46bSShawn Guo  * Copyright 2013 Freescale Semiconductor, Inc.
3ca3de46bSShawn Guo  *
4ca3de46bSShawn Guo  * This program is free software; you can redistribute it and/or modify
5ca3de46bSShawn Guo  * it under the terms of the GNU General Public License version 2 as
6ca3de46bSShawn Guo  * published by the Free Software Foundation.
7ca3de46bSShawn Guo  *
8ca3de46bSShawn Guo  */
9ca3de46bSShawn Guo 
10ca3de46bSShawn Guo #include <linux/cpu_cooling.h>
11ca3de46bSShawn Guo #include <linux/cpufreq.h>
12ca3de46bSShawn Guo #include <linux/delay.h>
13ca3de46bSShawn Guo #include <linux/device.h>
14ca3de46bSShawn Guo #include <linux/init.h>
1537713a1eSPhilipp Zabel #include <linux/interrupt.h>
16ca3de46bSShawn Guo #include <linux/io.h>
17ca3de46bSShawn Guo #include <linux/kernel.h>
18ca3de46bSShawn Guo #include <linux/mfd/syscon.h>
19ca3de46bSShawn Guo #include <linux/module.h>
20ca3de46bSShawn Guo #include <linux/of.h>
21ca3de46bSShawn Guo #include <linux/platform_device.h>
22ca3de46bSShawn Guo #include <linux/regmap.h>
23ca3de46bSShawn Guo #include <linux/slab.h>
24ca3de46bSShawn Guo #include <linux/thermal.h>
25ca3de46bSShawn Guo #include <linux/types.h>
26ca3de46bSShawn Guo 
27ca3de46bSShawn Guo #define REG_SET		0x4
28ca3de46bSShawn Guo #define REG_CLR		0x8
29ca3de46bSShawn Guo #define REG_TOG		0xc
30ca3de46bSShawn Guo 
31ca3de46bSShawn Guo #define MISC0				0x0150
32ca3de46bSShawn Guo #define MISC0_REFTOP_SELBIASOFF		(1 << 3)
33ca3de46bSShawn Guo 
34ca3de46bSShawn Guo #define TEMPSENSE0			0x0180
3537713a1eSPhilipp Zabel #define TEMPSENSE0_ALARM_VALUE_SHIFT	20
3637713a1eSPhilipp Zabel #define TEMPSENSE0_ALARM_VALUE_MASK	(0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT)
37ca3de46bSShawn Guo #define TEMPSENSE0_TEMP_CNT_SHIFT	8
38ca3de46bSShawn Guo #define TEMPSENSE0_TEMP_CNT_MASK	(0xfff << TEMPSENSE0_TEMP_CNT_SHIFT)
39ca3de46bSShawn Guo #define TEMPSENSE0_FINISHED		(1 << 2)
40ca3de46bSShawn Guo #define TEMPSENSE0_MEASURE_TEMP		(1 << 1)
41ca3de46bSShawn Guo #define TEMPSENSE0_POWER_DOWN		(1 << 0)
42ca3de46bSShawn Guo 
43ca3de46bSShawn Guo #define TEMPSENSE1			0x0190
44ca3de46bSShawn Guo #define TEMPSENSE1_MEASURE_FREQ		0xffff
45ca3de46bSShawn Guo 
46ca3de46bSShawn Guo #define OCOTP_ANA1			0x04e0
47ca3de46bSShawn Guo 
48ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */
49ca3de46bSShawn Guo enum imx_thermal_trip {
50ca3de46bSShawn Guo 	IMX_TRIP_PASSIVE,
51ca3de46bSShawn Guo 	IMX_TRIP_CRITICAL,
52ca3de46bSShawn Guo 	IMX_TRIP_NUM,
53ca3de46bSShawn Guo };
54ca3de46bSShawn Guo 
55ca3de46bSShawn Guo /*
56ca3de46bSShawn Guo  * It defines the temperature in millicelsius for passive trip point
57ca3de46bSShawn Guo  * that will trigger cooling action when crossed.
58ca3de46bSShawn Guo  */
59ca3de46bSShawn Guo #define IMX_TEMP_PASSIVE		85000
60ca3de46bSShawn Guo 
61ca3de46bSShawn Guo #define IMX_POLLING_DELAY		2000 /* millisecond */
62ca3de46bSShawn Guo #define IMX_PASSIVE_DELAY		1000
63ca3de46bSShawn Guo 
64ca3de46bSShawn Guo struct imx_thermal_data {
65ca3de46bSShawn Guo 	struct thermal_zone_device *tz;
66ca3de46bSShawn Guo 	struct thermal_cooling_device *cdev;
67ca3de46bSShawn Guo 	enum thermal_device_mode mode;
68ca3de46bSShawn Guo 	struct regmap *tempmon;
69ca3de46bSShawn Guo 	int c1, c2; /* See formula in imx_get_sensor_data() */
70017e5142SPhilipp Zabel 	unsigned long temp_passive;
71017e5142SPhilipp Zabel 	unsigned long temp_critical;
7237713a1eSPhilipp Zabel 	unsigned long alarm_temp;
7337713a1eSPhilipp Zabel 	unsigned long last_temp;
7437713a1eSPhilipp Zabel 	bool irq_enabled;
7537713a1eSPhilipp Zabel 	int irq;
76ca3de46bSShawn Guo };
77ca3de46bSShawn Guo 
7837713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data,
7937713a1eSPhilipp Zabel 			       signed long alarm_temp)
8037713a1eSPhilipp Zabel {
8137713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
8237713a1eSPhilipp Zabel 	int alarm_value;
8337713a1eSPhilipp Zabel 
8437713a1eSPhilipp Zabel 	data->alarm_temp = alarm_temp;
8537713a1eSPhilipp Zabel 	alarm_value = (alarm_temp - data->c2) / data->c1;
8637713a1eSPhilipp Zabel 	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_ALARM_VALUE_MASK);
8737713a1eSPhilipp Zabel 	regmap_write(map, TEMPSENSE0 + REG_SET, alarm_value <<
8837713a1eSPhilipp Zabel 			TEMPSENSE0_ALARM_VALUE_SHIFT);
8937713a1eSPhilipp Zabel }
9037713a1eSPhilipp Zabel 
91ca3de46bSShawn Guo static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
92ca3de46bSShawn Guo {
93ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
94ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
95ca3de46bSShawn Guo 	unsigned int n_meas;
9637713a1eSPhilipp Zabel 	bool wait;
97ca3de46bSShawn Guo 	u32 val;
98ca3de46bSShawn Guo 
9937713a1eSPhilipp Zabel 	if (data->mode == THERMAL_DEVICE_ENABLED) {
10037713a1eSPhilipp Zabel 		/* Check if a measurement is currently in progress */
10137713a1eSPhilipp Zabel 		regmap_read(map, TEMPSENSE0, &val);
10237713a1eSPhilipp Zabel 		wait = !(val & TEMPSENSE0_FINISHED);
10337713a1eSPhilipp Zabel 	} else {
104ca3de46bSShawn Guo 		/*
105ca3de46bSShawn Guo 		 * Every time we measure the temperature, we will power on the
106ca3de46bSShawn Guo 		 * temperature sensor, enable measurements, take a reading,
107ca3de46bSShawn Guo 		 * disable measurements, power off the temperature sensor.
108ca3de46bSShawn Guo 		 */
109ca3de46bSShawn Guo 		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
110ca3de46bSShawn Guo 		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
111ca3de46bSShawn Guo 
11237713a1eSPhilipp Zabel 		wait = true;
11337713a1eSPhilipp Zabel 	}
11437713a1eSPhilipp Zabel 
115ca3de46bSShawn Guo 	/*
116ca3de46bSShawn Guo 	 * According to the temp sensor designers, it may require up to ~17us
117ca3de46bSShawn Guo 	 * to complete a measurement.
118ca3de46bSShawn Guo 	 */
11937713a1eSPhilipp Zabel 	if (wait)
120ca3de46bSShawn Guo 		usleep_range(20, 50);
121ca3de46bSShawn Guo 
122ca3de46bSShawn Guo 	regmap_read(map, TEMPSENSE0, &val);
12337713a1eSPhilipp Zabel 
12437713a1eSPhilipp Zabel 	if (data->mode != THERMAL_DEVICE_ENABLED) {
125ca3de46bSShawn Guo 		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
126ca3de46bSShawn Guo 		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
12737713a1eSPhilipp Zabel 	}
128ca3de46bSShawn Guo 
129ca3de46bSShawn Guo 	if ((val & TEMPSENSE0_FINISHED) == 0) {
130ca3de46bSShawn Guo 		dev_dbg(&tz->device, "temp measurement never finished\n");
131ca3de46bSShawn Guo 		return -EAGAIN;
132ca3de46bSShawn Guo 	}
133ca3de46bSShawn Guo 
134ca3de46bSShawn Guo 	n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT;
135ca3de46bSShawn Guo 
136ca3de46bSShawn Guo 	/* See imx_get_sensor_data() for formula derivation */
137ca3de46bSShawn Guo 	*temp = data->c2 + data->c1 * n_meas;
138ca3de46bSShawn Guo 
13937713a1eSPhilipp Zabel 	/* Update alarm value to next higher trip point */
14037713a1eSPhilipp Zabel 	if (data->alarm_temp == data->temp_passive && *temp >= data->temp_passive)
14137713a1eSPhilipp Zabel 		imx_set_alarm_temp(data, data->temp_critical);
14237713a1eSPhilipp Zabel 	if (data->alarm_temp == data->temp_critical && *temp < data->temp_passive) {
14337713a1eSPhilipp Zabel 		imx_set_alarm_temp(data, data->temp_passive);
14437713a1eSPhilipp Zabel 		dev_dbg(&tz->device, "thermal alarm off: T < %lu\n",
14537713a1eSPhilipp Zabel 			data->alarm_temp / 1000);
14637713a1eSPhilipp Zabel 	}
14737713a1eSPhilipp Zabel 
14837713a1eSPhilipp Zabel 	if (*temp != data->last_temp) {
149ca3de46bSShawn Guo 		dev_dbg(&tz->device, "millicelsius: %ld\n", *temp);
15037713a1eSPhilipp Zabel 		data->last_temp = *temp;
15137713a1eSPhilipp Zabel 	}
15237713a1eSPhilipp Zabel 
15337713a1eSPhilipp Zabel 	/* Reenable alarm IRQ if temperature below alarm temperature */
15437713a1eSPhilipp Zabel 	if (!data->irq_enabled && *temp < data->alarm_temp) {
15537713a1eSPhilipp Zabel 		data->irq_enabled = true;
15637713a1eSPhilipp Zabel 		enable_irq(data->irq);
157ca3de46bSShawn Guo 	}
158ca3de46bSShawn Guo 
159ca3de46bSShawn Guo 	return 0;
160ca3de46bSShawn Guo }
161ca3de46bSShawn Guo 
162ca3de46bSShawn Guo static int imx_get_mode(struct thermal_zone_device *tz,
163ca3de46bSShawn Guo 			enum thermal_device_mode *mode)
164ca3de46bSShawn Guo {
165ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
166ca3de46bSShawn Guo 
167ca3de46bSShawn Guo 	*mode = data->mode;
168ca3de46bSShawn Guo 
169ca3de46bSShawn Guo 	return 0;
170ca3de46bSShawn Guo }
171ca3de46bSShawn Guo 
172ca3de46bSShawn Guo static int imx_set_mode(struct thermal_zone_device *tz,
173ca3de46bSShawn Guo 			enum thermal_device_mode mode)
174ca3de46bSShawn Guo {
175ca3de46bSShawn Guo 	struct imx_thermal_data *data = tz->devdata;
17637713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
177ca3de46bSShawn Guo 
178ca3de46bSShawn Guo 	if (mode == THERMAL_DEVICE_ENABLED) {
179ca3de46bSShawn Guo 		tz->polling_delay = IMX_POLLING_DELAY;
180ca3de46bSShawn Guo 		tz->passive_delay = IMX_PASSIVE_DELAY;
18137713a1eSPhilipp Zabel 
18237713a1eSPhilipp Zabel 		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
18337713a1eSPhilipp Zabel 		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
18437713a1eSPhilipp Zabel 
18537713a1eSPhilipp Zabel 		if (!data->irq_enabled) {
18637713a1eSPhilipp Zabel 			data->irq_enabled = true;
18737713a1eSPhilipp Zabel 			enable_irq(data->irq);
18837713a1eSPhilipp Zabel 		}
189ca3de46bSShawn Guo 	} else {
19037713a1eSPhilipp Zabel 		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
19137713a1eSPhilipp Zabel 		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
19237713a1eSPhilipp Zabel 
193ca3de46bSShawn Guo 		tz->polling_delay = 0;
194ca3de46bSShawn Guo 		tz->passive_delay = 0;
19537713a1eSPhilipp Zabel 
19637713a1eSPhilipp Zabel 		if (data->irq_enabled) {
19737713a1eSPhilipp Zabel 			disable_irq(data->irq);
19837713a1eSPhilipp Zabel 			data->irq_enabled = false;
19937713a1eSPhilipp Zabel 		}
200ca3de46bSShawn Guo 	}
201ca3de46bSShawn Guo 
202ca3de46bSShawn Guo 	data->mode = mode;
203ca3de46bSShawn Guo 	thermal_zone_device_update(tz);
204ca3de46bSShawn Guo 
205ca3de46bSShawn Guo 	return 0;
206ca3de46bSShawn Guo }
207ca3de46bSShawn Guo 
208ca3de46bSShawn Guo static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
209ca3de46bSShawn Guo 			     enum thermal_trip_type *type)
210ca3de46bSShawn Guo {
211ca3de46bSShawn Guo 	*type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
212ca3de46bSShawn Guo 					     THERMAL_TRIP_CRITICAL;
213ca3de46bSShawn Guo 	return 0;
214ca3de46bSShawn Guo }
215ca3de46bSShawn Guo 
216ca3de46bSShawn Guo static int imx_get_crit_temp(struct thermal_zone_device *tz,
217ca3de46bSShawn Guo 			     unsigned long *temp)
218ca3de46bSShawn Guo {
219017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
220017e5142SPhilipp Zabel 
221017e5142SPhilipp Zabel 	*temp = data->temp_critical;
222ca3de46bSShawn Guo 	return 0;
223ca3de46bSShawn Guo }
224ca3de46bSShawn Guo 
225ca3de46bSShawn Guo static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
226ca3de46bSShawn Guo 			     unsigned long *temp)
227ca3de46bSShawn Guo {
228017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
229017e5142SPhilipp Zabel 
230017e5142SPhilipp Zabel 	*temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive :
231017e5142SPhilipp Zabel 					     data->temp_critical;
232017e5142SPhilipp Zabel 	return 0;
233017e5142SPhilipp Zabel }
234017e5142SPhilipp Zabel 
235017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
236017e5142SPhilipp Zabel 			     unsigned long temp)
237017e5142SPhilipp Zabel {
238017e5142SPhilipp Zabel 	struct imx_thermal_data *data = tz->devdata;
239017e5142SPhilipp Zabel 
240017e5142SPhilipp Zabel 	if (trip == IMX_TRIP_CRITICAL)
241017e5142SPhilipp Zabel 		return -EPERM;
242017e5142SPhilipp Zabel 
243017e5142SPhilipp Zabel 	if (temp > IMX_TEMP_PASSIVE)
244017e5142SPhilipp Zabel 		return -EINVAL;
245017e5142SPhilipp Zabel 
246017e5142SPhilipp Zabel 	data->temp_passive = temp;
247017e5142SPhilipp Zabel 
24837713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, temp);
24937713a1eSPhilipp Zabel 
250ca3de46bSShawn Guo 	return 0;
251ca3de46bSShawn Guo }
252ca3de46bSShawn Guo 
253ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
254ca3de46bSShawn Guo 		    struct thermal_cooling_device *cdev)
255ca3de46bSShawn Guo {
256ca3de46bSShawn Guo 	int ret;
257ca3de46bSShawn Guo 
258ca3de46bSShawn Guo 	ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
259ca3de46bSShawn Guo 					       THERMAL_NO_LIMIT,
260ca3de46bSShawn Guo 					       THERMAL_NO_LIMIT);
261ca3de46bSShawn Guo 	if (ret) {
262ca3de46bSShawn Guo 		dev_err(&tz->device,
263ca3de46bSShawn Guo 			"binding zone %s with cdev %s failed:%d\n",
264ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
265ca3de46bSShawn Guo 		return ret;
266ca3de46bSShawn Guo 	}
267ca3de46bSShawn Guo 
268ca3de46bSShawn Guo 	return 0;
269ca3de46bSShawn Guo }
270ca3de46bSShawn Guo 
271ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
272ca3de46bSShawn Guo 		      struct thermal_cooling_device *cdev)
273ca3de46bSShawn Guo {
274ca3de46bSShawn Guo 	int ret;
275ca3de46bSShawn Guo 
276ca3de46bSShawn Guo 	ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
277ca3de46bSShawn Guo 	if (ret) {
278ca3de46bSShawn Guo 		dev_err(&tz->device,
279ca3de46bSShawn Guo 			"unbinding zone %s with cdev %s failed:%d\n",
280ca3de46bSShawn Guo 			tz->type, cdev->type, ret);
281ca3de46bSShawn Guo 		return ret;
282ca3de46bSShawn Guo 	}
283ca3de46bSShawn Guo 
284ca3de46bSShawn Guo 	return 0;
285ca3de46bSShawn Guo }
286ca3de46bSShawn Guo 
287cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
288ca3de46bSShawn Guo 	.bind = imx_bind,
289ca3de46bSShawn Guo 	.unbind = imx_unbind,
290ca3de46bSShawn Guo 	.get_temp = imx_get_temp,
291ca3de46bSShawn Guo 	.get_mode = imx_get_mode,
292ca3de46bSShawn Guo 	.set_mode = imx_set_mode,
293ca3de46bSShawn Guo 	.get_trip_type = imx_get_trip_type,
294ca3de46bSShawn Guo 	.get_trip_temp = imx_get_trip_temp,
295ca3de46bSShawn Guo 	.get_crit_temp = imx_get_crit_temp,
296017e5142SPhilipp Zabel 	.set_trip_temp = imx_set_trip_temp,
297ca3de46bSShawn Guo };
298ca3de46bSShawn Guo 
299ca3de46bSShawn Guo static int imx_get_sensor_data(struct platform_device *pdev)
300ca3de46bSShawn Guo {
301ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
302ca3de46bSShawn Guo 	struct regmap *map;
303ca3de46bSShawn Guo 	int t1, t2, n1, n2;
304ca3de46bSShawn Guo 	int ret;
305ca3de46bSShawn Guo 	u32 val;
306ca3de46bSShawn Guo 
307ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
308ca3de46bSShawn Guo 					      "fsl,tempmon-data");
309ca3de46bSShawn Guo 	if (IS_ERR(map)) {
310ca3de46bSShawn Guo 		ret = PTR_ERR(map);
311ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
312ca3de46bSShawn Guo 		return ret;
313ca3de46bSShawn Guo 	}
314ca3de46bSShawn Guo 
315ca3de46bSShawn Guo 	ret = regmap_read(map, OCOTP_ANA1, &val);
316ca3de46bSShawn Guo 	if (ret) {
317ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
318ca3de46bSShawn Guo 		return ret;
319ca3de46bSShawn Guo 	}
320ca3de46bSShawn Guo 
321ca3de46bSShawn Guo 	if (val == 0 || val == ~0) {
322ca3de46bSShawn Guo 		dev_err(&pdev->dev, "invalid sensor calibration data\n");
323ca3de46bSShawn Guo 		return -EINVAL;
324ca3de46bSShawn Guo 	}
325ca3de46bSShawn Guo 
326ca3de46bSShawn Guo 	/*
327ca3de46bSShawn Guo 	 * Sensor data layout:
328ca3de46bSShawn Guo 	 *   [31:20] - sensor value @ 25C
329ca3de46bSShawn Guo 	 *    [19:8] - sensor value of hot
330ca3de46bSShawn Guo 	 *     [7:0] - hot temperature value
331ca3de46bSShawn Guo 	 */
332ca3de46bSShawn Guo 	n1 = val >> 20;
333ca3de46bSShawn Guo 	n2 = (val & 0xfff00) >> 8;
334ca3de46bSShawn Guo 	t2 = val & 0xff;
335ca3de46bSShawn Guo 	t1 = 25; /* t1 always 25C */
336ca3de46bSShawn Guo 
337ca3de46bSShawn Guo 	/*
338ca3de46bSShawn Guo 	 * Derived from linear interpolation,
339ca3de46bSShawn Guo 	 * Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2)
340ca3de46bSShawn Guo 	 * We want to reduce this down to the minimum computation necessary
341ca3de46bSShawn Guo 	 * for each temperature read.  Also, we want Tmeas in millicelsius
342ca3de46bSShawn Guo 	 * and we don't want to lose precision from integer division. So...
343ca3de46bSShawn Guo 	 * milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2)
344ca3de46bSShawn Guo 	 * Let constant c1 = 1000 * (T1 - T2) / (N1 - N2)
345ca3de46bSShawn Guo 	 * milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2)
346ca3de46bSShawn Guo 	 * milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2)
347ca3de46bSShawn Guo 	 * Let constant c2 = (1000 * T2) - (c1 * N2)
348ca3de46bSShawn Guo 	 * milli_Tmeas = c2 + (c1 * Nmeas)
349ca3de46bSShawn Guo 	 */
350ca3de46bSShawn Guo 	data->c1 = 1000 * (t1 - t2) / (n1 - n2);
351ca3de46bSShawn Guo 	data->c2 = 1000 * t2 - data->c1 * n2;
352ca3de46bSShawn Guo 
353017e5142SPhilipp Zabel 	/*
354017e5142SPhilipp Zabel 	 * Set the default passive cooling trip point to 20 °C below the
355017e5142SPhilipp Zabel 	 * maximum die temperature. Can be changed from userspace.
356017e5142SPhilipp Zabel 	 */
357017e5142SPhilipp Zabel 	data->temp_passive = 1000 * (t2 - 20);
358017e5142SPhilipp Zabel 
359017e5142SPhilipp Zabel 	/*
360017e5142SPhilipp Zabel 	 * The maximum die temperature is t2, let's give 5 °C cushion
361017e5142SPhilipp Zabel 	 * for noise and possible temperature rise between measurements.
362017e5142SPhilipp Zabel 	 */
363017e5142SPhilipp Zabel 	data->temp_critical = 1000 * (t2 - 5);
364017e5142SPhilipp Zabel 
365ca3de46bSShawn Guo 	return 0;
366ca3de46bSShawn Guo }
367ca3de46bSShawn Guo 
36837713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
36937713a1eSPhilipp Zabel {
37037713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
37137713a1eSPhilipp Zabel 
37237713a1eSPhilipp Zabel 	disable_irq_nosync(irq);
37337713a1eSPhilipp Zabel 	data->irq_enabled = false;
37437713a1eSPhilipp Zabel 
37537713a1eSPhilipp Zabel 	return IRQ_WAKE_THREAD;
37637713a1eSPhilipp Zabel }
37737713a1eSPhilipp Zabel 
37837713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
37937713a1eSPhilipp Zabel {
38037713a1eSPhilipp Zabel 	struct imx_thermal_data *data = dev;
38137713a1eSPhilipp Zabel 
38237713a1eSPhilipp Zabel 	dev_dbg(&data->tz->device, "THERMAL ALARM: T > %lu\n",
38337713a1eSPhilipp Zabel 		data->alarm_temp / 1000);
38437713a1eSPhilipp Zabel 
38537713a1eSPhilipp Zabel 	thermal_zone_device_update(data->tz);
38637713a1eSPhilipp Zabel 
38737713a1eSPhilipp Zabel 	return IRQ_HANDLED;
38837713a1eSPhilipp Zabel }
38937713a1eSPhilipp Zabel 
390ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
391ca3de46bSShawn Guo {
392ca3de46bSShawn Guo 	struct imx_thermal_data *data;
393ca3de46bSShawn Guo 	struct cpumask clip_cpus;
394ca3de46bSShawn Guo 	struct regmap *map;
39537713a1eSPhilipp Zabel 	int measure_freq;
396ca3de46bSShawn Guo 	int ret;
397ca3de46bSShawn Guo 
398ca3de46bSShawn Guo 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
399ca3de46bSShawn Guo 	if (!data)
400ca3de46bSShawn Guo 		return -ENOMEM;
401ca3de46bSShawn Guo 
402ca3de46bSShawn Guo 	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
403ca3de46bSShawn Guo 	if (IS_ERR(map)) {
404ca3de46bSShawn Guo 		ret = PTR_ERR(map);
405ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
406ca3de46bSShawn Guo 		return ret;
407ca3de46bSShawn Guo 	}
408ca3de46bSShawn Guo 	data->tempmon = map;
409ca3de46bSShawn Guo 
41037713a1eSPhilipp Zabel 	data->irq = platform_get_irq(pdev, 0);
41137713a1eSPhilipp Zabel 	if (data->irq < 0)
41237713a1eSPhilipp Zabel 		return data->irq;
41337713a1eSPhilipp Zabel 
41437713a1eSPhilipp Zabel 	ret = devm_request_threaded_irq(&pdev->dev, data->irq,
41537713a1eSPhilipp Zabel 			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
41637713a1eSPhilipp Zabel 			0, "imx_thermal", data);
41737713a1eSPhilipp Zabel 	if (ret < 0) {
41837713a1eSPhilipp Zabel 		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
41937713a1eSPhilipp Zabel 		return ret;
42037713a1eSPhilipp Zabel 	}
42137713a1eSPhilipp Zabel 
422ca3de46bSShawn Guo 	platform_set_drvdata(pdev, data);
423ca3de46bSShawn Guo 
424ca3de46bSShawn Guo 	ret = imx_get_sensor_data(pdev);
425ca3de46bSShawn Guo 	if (ret) {
426ca3de46bSShawn Guo 		dev_err(&pdev->dev, "failed to get sensor data\n");
427ca3de46bSShawn Guo 		return ret;
428ca3de46bSShawn Guo 	}
429ca3de46bSShawn Guo 
430ca3de46bSShawn Guo 	/* Make sure sensor is in known good state for measurements */
431ca3de46bSShawn Guo 	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
432ca3de46bSShawn Guo 	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP);
433ca3de46bSShawn Guo 	regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ);
434ca3de46bSShawn Guo 	regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF);
435ca3de46bSShawn Guo 	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
436ca3de46bSShawn Guo 
437ca3de46bSShawn Guo 	cpumask_set_cpu(0, &clip_cpus);
438ca3de46bSShawn Guo 	data->cdev = cpufreq_cooling_register(&clip_cpus);
439ca3de46bSShawn Guo 	if (IS_ERR(data->cdev)) {
440ca3de46bSShawn Guo 		ret = PTR_ERR(data->cdev);
441ca3de46bSShawn Guo 		dev_err(&pdev->dev,
442ca3de46bSShawn Guo 			"failed to register cpufreq cooling device: %d\n", ret);
443ca3de46bSShawn Guo 		return ret;
444ca3de46bSShawn Guo 	}
445ca3de46bSShawn Guo 
446ca3de46bSShawn Guo 	data->tz = thermal_zone_device_register("imx_thermal_zone",
447017e5142SPhilipp Zabel 						IMX_TRIP_NUM,
448017e5142SPhilipp Zabel 						BIT(IMX_TRIP_PASSIVE), data,
449ca3de46bSShawn Guo 						&imx_tz_ops, NULL,
450ca3de46bSShawn Guo 						IMX_PASSIVE_DELAY,
451ca3de46bSShawn Guo 						IMX_POLLING_DELAY);
452ca3de46bSShawn Guo 	if (IS_ERR(data->tz)) {
453ca3de46bSShawn Guo 		ret = PTR_ERR(data->tz);
454ca3de46bSShawn Guo 		dev_err(&pdev->dev,
455ca3de46bSShawn Guo 			"failed to register thermal zone device %d\n", ret);
456ca3de46bSShawn Guo 		cpufreq_cooling_unregister(data->cdev);
457ca3de46bSShawn Guo 		return ret;
458ca3de46bSShawn Guo 	}
459ca3de46bSShawn Guo 
46037713a1eSPhilipp Zabel 	/* Enable measurements at ~ 10 Hz */
46137713a1eSPhilipp Zabel 	regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ);
46237713a1eSPhilipp Zabel 	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
46337713a1eSPhilipp Zabel 	regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq);
46437713a1eSPhilipp Zabel 	imx_set_alarm_temp(data, data->temp_passive);
46537713a1eSPhilipp Zabel 	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
46637713a1eSPhilipp Zabel 	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
46737713a1eSPhilipp Zabel 
46837713a1eSPhilipp Zabel 	data->irq_enabled = true;
469ca3de46bSShawn Guo 	data->mode = THERMAL_DEVICE_ENABLED;
470ca3de46bSShawn Guo 
471ca3de46bSShawn Guo 	return 0;
472ca3de46bSShawn Guo }
473ca3de46bSShawn Guo 
474ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
475ca3de46bSShawn Guo {
476ca3de46bSShawn Guo 	struct imx_thermal_data *data = platform_get_drvdata(pdev);
47737713a1eSPhilipp Zabel 	struct regmap *map = data->tempmon;
47837713a1eSPhilipp Zabel 
47937713a1eSPhilipp Zabel 	/* Disable measurements */
48037713a1eSPhilipp Zabel 	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN);
481ca3de46bSShawn Guo 
482ca3de46bSShawn Guo 	thermal_zone_device_unregister(data->tz);
483ca3de46bSShawn Guo 	cpufreq_cooling_unregister(data->cdev);
484ca3de46bSShawn Guo 
485ca3de46bSShawn Guo 	return 0;
486ca3de46bSShawn Guo }
487ca3de46bSShawn Guo 
488ca3de46bSShawn Guo #ifdef CONFIG_PM_SLEEP
489ca3de46bSShawn Guo static int imx_thermal_suspend(struct device *dev)
490ca3de46bSShawn Guo {
491ca3de46bSShawn Guo 	struct imx_thermal_data *data = dev_get_drvdata(dev);
492ca3de46bSShawn Guo 	struct regmap *map = data->tempmon;
493ca3de46bSShawn Guo 	u32 val;
494ca3de46bSShawn Guo 
495ca3de46bSShawn Guo 	regmap_read(map, TEMPSENSE0, &val);
496ca3de46bSShawn Guo 	if ((val & TEMPSENSE0_POWER_DOWN) == 0) {
497ca3de46bSShawn Guo 		/*
498ca3de46bSShawn Guo 		 * If a measurement is taking place, wait for a long enough
499ca3de46bSShawn Guo 		 * time for it to finish, and then check again.  If it still
500ca3de46bSShawn Guo 		 * does not finish, something must go wrong.
501ca3de46bSShawn Guo 		 */
502ca3de46bSShawn Guo 		udelay(50);
503ca3de46bSShawn Guo 		regmap_read(map, TEMPSENSE0, &val);
504ca3de46bSShawn Guo 		if ((val & TEMPSENSE0_POWER_DOWN) == 0)
505ca3de46bSShawn Guo 			return -ETIMEDOUT;
506ca3de46bSShawn Guo 	}
507ca3de46bSShawn Guo 
508ca3de46bSShawn Guo 	return 0;
509ca3de46bSShawn Guo }
510ca3de46bSShawn Guo 
511ca3de46bSShawn Guo static int imx_thermal_resume(struct device *dev)
512ca3de46bSShawn Guo {
513ca3de46bSShawn Guo 	/* Nothing to do for now */
514ca3de46bSShawn Guo 	return 0;
515ca3de46bSShawn Guo }
516ca3de46bSShawn Guo #endif
517ca3de46bSShawn Guo 
518ca3de46bSShawn Guo static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
519ca3de46bSShawn Guo 			 imx_thermal_suspend, imx_thermal_resume);
520ca3de46bSShawn Guo 
521ca3de46bSShawn Guo static const struct of_device_id of_imx_thermal_match[] = {
522ca3de46bSShawn Guo 	{ .compatible = "fsl,imx6q-tempmon", },
523ca3de46bSShawn Guo 	{ /* end */ }
524ca3de46bSShawn Guo };
525ca3de46bSShawn Guo 
526ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
527ca3de46bSShawn Guo 	.driver = {
528ca3de46bSShawn Guo 		.name	= "imx_thermal",
529ca3de46bSShawn Guo 		.owner  = THIS_MODULE,
530ca3de46bSShawn Guo 		.pm	= &imx_thermal_pm_ops,
531ca3de46bSShawn Guo 		.of_match_table = of_imx_thermal_match,
532ca3de46bSShawn Guo 	},
533ca3de46bSShawn Guo 	.probe		= imx_thermal_probe,
534ca3de46bSShawn Guo 	.remove		= imx_thermal_remove,
535ca3de46bSShawn Guo };
536ca3de46bSShawn Guo module_platform_driver(imx_thermal);
537ca3de46bSShawn Guo 
538ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
539ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
540ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
541ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
542