145f8b0ddSFabio Estevam // SPDX-License-Identifier: GPL-2.0
245f8b0ddSFabio Estevam //
345f8b0ddSFabio Estevam // Copyright 2013 Freescale Semiconductor, Inc.
4ca3de46bSShawn Guo
5329fe7b1SAnson Huang #include <linux/clk.h>
64d753aa7SViresh Kumar #include <linux/cpufreq.h>
7ca3de46bSShawn Guo #include <linux/cpu_cooling.h>
8ca3de46bSShawn Guo #include <linux/delay.h>
937713a1eSPhilipp Zabel #include <linux/interrupt.h>
10ca3de46bSShawn Guo #include <linux/io.h>
11ca3de46bSShawn Guo #include <linux/mfd/syscon.h>
12ca3de46bSShawn Guo #include <linux/module.h>
13ca3de46bSShawn Guo #include <linux/of.h>
14*f6a756e8SRob Herring #include <linux/platform_device.h>
15ca3de46bSShawn Guo #include <linux/regmap.h>
16ca3de46bSShawn Guo #include <linux/thermal.h>
17ae621557SLeonard Crestez #include <linux/nvmem-consumer.h>
184cf2ddf1SOleksij Rempel #include <linux/pm_runtime.h>
19ca3de46bSShawn Guo
20ca3de46bSShawn Guo #define REG_SET 0x4
21ca3de46bSShawn Guo #define REG_CLR 0x8
22ca3de46bSShawn Guo #define REG_TOG 0xc
23ca3de46bSShawn Guo
24f085f672SAnson Huang /* i.MX6 specific */
25f085f672SAnson Huang #define IMX6_MISC0 0x0150
26f085f672SAnson Huang #define IMX6_MISC0_REFTOP_SELBIASOFF (1 << 3)
27f085f672SAnson Huang #define IMX6_MISC1 0x0160
28f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPHIGH (1 << 29)
293c94f17eSAnson Huang /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */
30f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPLOW (1 << 28)
31f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPPANIC (1 << 27)
32ca3de46bSShawn Guo
33f085f672SAnson Huang #define IMX6_TEMPSENSE0 0x0180
34f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT 20
35f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_MASK (0xfff << 20)
36f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT 8
37f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_MASK (0xfff << 8)
38f085f672SAnson Huang #define IMX6_TEMPSENSE0_FINISHED (1 << 2)
39f085f672SAnson Huang #define IMX6_TEMPSENSE0_MEASURE_TEMP (1 << 1)
40f085f672SAnson Huang #define IMX6_TEMPSENSE0_POWER_DOWN (1 << 0)
41ca3de46bSShawn Guo
42f085f672SAnson Huang #define IMX6_TEMPSENSE1 0x0190
43f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ 0xffff
44f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT 0
45ca3de46bSShawn Guo
46a2291badSTim Harvey #define OCOTP_MEM0 0x0480
47ca3de46bSShawn Guo #define OCOTP_ANA1 0x04e0
48ca3de46bSShawn Guo
49f085f672SAnson Huang /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */
50f085f672SAnson Huang #define IMX6_TEMPSENSE2 0x0290
51f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT 0
52f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_MASK 0xfff
53f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT 16
54f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000
55f085f672SAnson Huang
56f085f672SAnson Huang /* i.MX7 specific */
57f085f672SAnson Huang #define IMX7_ANADIG_DIGPROG 0x800
58f085f672SAnson Huang #define IMX7_TEMPSENSE0 0x300
59f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT 18
60f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_MASK (0x1ff << 18)
61f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT 9
62f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_MASK (0x1ff << 9)
63f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT 0
64f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_MASK 0x1ff
65f085f672SAnson Huang
66f085f672SAnson Huang #define IMX7_TEMPSENSE1 0x310
67f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT 16
68f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK (0xffff << 16)
69f085f672SAnson Huang #define IMX7_TEMPSENSE1_FINISHED (1 << 11)
70f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_TEMP (1 << 10)
71f085f672SAnson Huang #define IMX7_TEMPSENSE1_POWER_DOWN (1 << 9)
72f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT 0
73f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_MASK 0x1ff
74f085f672SAnson Huang
75ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */
76ca3de46bSShawn Guo enum imx_thermal_trip {
77ca3de46bSShawn Guo IMX_TRIP_PASSIVE,
78ca3de46bSShawn Guo IMX_TRIP_CRITICAL,
79ca3de46bSShawn Guo };
80ca3de46bSShawn Guo
81ca3de46bSShawn Guo #define IMX_POLLING_DELAY 2000 /* millisecond */
82ca3de46bSShawn Guo #define IMX_PASSIVE_DELAY 1000
83ca3de46bSShawn Guo
843c94f17eSAnson Huang #define TEMPMON_IMX6Q 1
853c94f17eSAnson Huang #define TEMPMON_IMX6SX 2
86f085f672SAnson Huang #define TEMPMON_IMX7D 3
873c94f17eSAnson Huang
883c94f17eSAnson Huang struct thermal_soc_data {
893c94f17eSAnson Huang u32 version;
90f085f672SAnson Huang
91f085f672SAnson Huang u32 sensor_ctrl;
92f085f672SAnson Huang u32 power_down_mask;
93f085f672SAnson Huang u32 measure_temp_mask;
94f085f672SAnson Huang
95f085f672SAnson Huang u32 measure_freq_ctrl;
96f085f672SAnson Huang u32 measure_freq_mask;
97f085f672SAnson Huang u32 measure_freq_shift;
98f085f672SAnson Huang
99f085f672SAnson Huang u32 temp_data;
100f085f672SAnson Huang u32 temp_value_mask;
101f085f672SAnson Huang u32 temp_value_shift;
102f085f672SAnson Huang u32 temp_valid_mask;
103f085f672SAnson Huang
104f085f672SAnson Huang u32 panic_alarm_ctrl;
105f085f672SAnson Huang u32 panic_alarm_mask;
106f085f672SAnson Huang u32 panic_alarm_shift;
107f085f672SAnson Huang
108f085f672SAnson Huang u32 high_alarm_ctrl;
109f085f672SAnson Huang u32 high_alarm_mask;
110f085f672SAnson Huang u32 high_alarm_shift;
111f085f672SAnson Huang
112f085f672SAnson Huang u32 low_alarm_ctrl;
113f085f672SAnson Huang u32 low_alarm_mask;
114f085f672SAnson Huang u32 low_alarm_shift;
1153c94f17eSAnson Huang };
1163c94f17eSAnson Huang
11730233a22SDaniel Lezcano static struct thermal_trip trips[] = {
11830233a22SDaniel Lezcano [IMX_TRIP_PASSIVE] = { .type = THERMAL_TRIP_PASSIVE },
11930233a22SDaniel Lezcano [IMX_TRIP_CRITICAL] = { .type = THERMAL_TRIP_CRITICAL },
12030233a22SDaniel Lezcano };
12130233a22SDaniel Lezcano
1223c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6q_data = {
1233c94f17eSAnson Huang .version = TEMPMON_IMX6Q,
124f085f672SAnson Huang
125f085f672SAnson Huang .sensor_ctrl = IMX6_TEMPSENSE0,
126f085f672SAnson Huang .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
127f085f672SAnson Huang .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
128f085f672SAnson Huang
129f085f672SAnson Huang .measure_freq_ctrl = IMX6_TEMPSENSE1,
130f085f672SAnson Huang .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
131f085f672SAnson Huang .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
132f085f672SAnson Huang
133f085f672SAnson Huang .temp_data = IMX6_TEMPSENSE0,
134f085f672SAnson Huang .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
135f085f672SAnson Huang .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
136f085f672SAnson Huang .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
137f085f672SAnson Huang
138f085f672SAnson Huang .high_alarm_ctrl = IMX6_TEMPSENSE0,
139f085f672SAnson Huang .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
140f085f672SAnson Huang .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
1413c94f17eSAnson Huang };
1423c94f17eSAnson Huang
1433c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6sx_data = {
1443c94f17eSAnson Huang .version = TEMPMON_IMX6SX,
145f085f672SAnson Huang
146f085f672SAnson Huang .sensor_ctrl = IMX6_TEMPSENSE0,
147f085f672SAnson Huang .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
148f085f672SAnson Huang .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
149f085f672SAnson Huang
150f085f672SAnson Huang .measure_freq_ctrl = IMX6_TEMPSENSE1,
151f085f672SAnson Huang .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
152f085f672SAnson Huang .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
153f085f672SAnson Huang
154f085f672SAnson Huang .temp_data = IMX6_TEMPSENSE0,
155f085f672SAnson Huang .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
156f085f672SAnson Huang .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
157f085f672SAnson Huang .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
158f085f672SAnson Huang
159f085f672SAnson Huang .high_alarm_ctrl = IMX6_TEMPSENSE0,
160f085f672SAnson Huang .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
161f085f672SAnson Huang .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
162f085f672SAnson Huang
163f085f672SAnson Huang .panic_alarm_ctrl = IMX6_TEMPSENSE2,
164f085f672SAnson Huang .panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK,
165f085f672SAnson Huang .panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT,
166f085f672SAnson Huang
167f085f672SAnson Huang .low_alarm_ctrl = IMX6_TEMPSENSE2,
168f085f672SAnson Huang .low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK,
169f085f672SAnson Huang .low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT,
170f085f672SAnson Huang };
171f085f672SAnson Huang
172f085f672SAnson Huang static struct thermal_soc_data thermal_imx7d_data = {
173f085f672SAnson Huang .version = TEMPMON_IMX7D,
174f085f672SAnson Huang
175f085f672SAnson Huang .sensor_ctrl = IMX7_TEMPSENSE1,
176f085f672SAnson Huang .power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN,
177f085f672SAnson Huang .measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP,
178f085f672SAnson Huang
179f085f672SAnson Huang .measure_freq_ctrl = IMX7_TEMPSENSE1,
180f085f672SAnson Huang .measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT,
181f085f672SAnson Huang .measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK,
182f085f672SAnson Huang
183f085f672SAnson Huang .temp_data = IMX7_TEMPSENSE1,
184f085f672SAnson Huang .temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK,
185f085f672SAnson Huang .temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT,
186f085f672SAnson Huang .temp_valid_mask = IMX7_TEMPSENSE1_FINISHED,
187f085f672SAnson Huang
188f085f672SAnson Huang .panic_alarm_ctrl = IMX7_TEMPSENSE1,
189f085f672SAnson Huang .panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK,
190f085f672SAnson Huang .panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT,
191f085f672SAnson Huang
192f085f672SAnson Huang .high_alarm_ctrl = IMX7_TEMPSENSE0,
193f085f672SAnson Huang .high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK,
194f085f672SAnson Huang .high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT,
195f085f672SAnson Huang
196f085f672SAnson Huang .low_alarm_ctrl = IMX7_TEMPSENSE0,
197f085f672SAnson Huang .low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK,
198f085f672SAnson Huang .low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT,
1993c94f17eSAnson Huang };
2003c94f17eSAnson Huang
201ca3de46bSShawn Guo struct imx_thermal_data {
2024cf2ddf1SOleksij Rempel struct device *dev;
2034d753aa7SViresh Kumar struct cpufreq_policy *policy;
204ca3de46bSShawn Guo struct thermal_zone_device *tz;
205ca3de46bSShawn Guo struct thermal_cooling_device *cdev;
206ca3de46bSShawn Guo struct regmap *tempmon;
207ae621557SLeonard Crestez u32 c1, c2; /* See formula in imx_init_calib() */
208a2291badSTim Harvey int temp_max;
20917e8351aSSascha Hauer int alarm_temp;
21017e8351aSSascha Hauer int last_temp;
21137713a1eSPhilipp Zabel bool irq_enabled;
21237713a1eSPhilipp Zabel int irq;
213329fe7b1SAnson Huang struct clk *thermal_clk;
2143c94f17eSAnson Huang const struct thermal_soc_data *socdata;
215a2291badSTim Harvey const char *temp_grade;
216ca3de46bSShawn Guo };
217ca3de46bSShawn Guo
imx_set_panic_temp(struct imx_thermal_data * data,int panic_temp)2183c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data,
21917e8351aSSascha Hauer int panic_temp)
2203c94f17eSAnson Huang {
221f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata;
2223c94f17eSAnson Huang struct regmap *map = data->tempmon;
2233c94f17eSAnson Huang int critical_value;
2243c94f17eSAnson Huang
2253c94f17eSAnson Huang critical_value = (data->c2 - panic_temp) / data->c1;
226f085f672SAnson Huang
227f085f672SAnson Huang regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR,
228f085f672SAnson Huang soc_data->panic_alarm_mask);
229f085f672SAnson Huang regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET,
230f085f672SAnson Huang critical_value << soc_data->panic_alarm_shift);
2313c94f17eSAnson Huang }
2323c94f17eSAnson Huang
imx_set_alarm_temp(struct imx_thermal_data * data,int alarm_temp)23337713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data,
23417e8351aSSascha Hauer int alarm_temp)
23537713a1eSPhilipp Zabel {
23637713a1eSPhilipp Zabel struct regmap *map = data->tempmon;
237f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata;
23837713a1eSPhilipp Zabel int alarm_value;
23937713a1eSPhilipp Zabel
24037713a1eSPhilipp Zabel data->alarm_temp = alarm_temp;
241f085f672SAnson Huang
242f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D)
243f085f672SAnson Huang alarm_value = alarm_temp / 1000 + data->c1 - 25;
244f085f672SAnson Huang else
245749e8be7SAnson Huang alarm_value = (data->c2 - alarm_temp) / data->c1;
246f085f672SAnson Huang
247f085f672SAnson Huang regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR,
248f085f672SAnson Huang soc_data->high_alarm_mask);
249f085f672SAnson Huang regmap_write(map, soc_data->high_alarm_ctrl + REG_SET,
250f085f672SAnson Huang alarm_value << soc_data->high_alarm_shift);
25137713a1eSPhilipp Zabel }
25237713a1eSPhilipp Zabel
imx_get_temp(struct thermal_zone_device * tz,int * temp)25317e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
254ca3de46bSShawn Guo {
2555f68d078SDaniel Lezcano struct imx_thermal_data *data = thermal_zone_device_priv(tz);
256f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata;
257ca3de46bSShawn Guo struct regmap *map = data->tempmon;
258ca3de46bSShawn Guo unsigned int n_meas;
259ca3de46bSShawn Guo u32 val;
2604cf2ddf1SOleksij Rempel int ret;
261ca3de46bSShawn Guo
2624cf2ddf1SOleksij Rempel ret = pm_runtime_resume_and_get(data->dev);
2634cf2ddf1SOleksij Rempel if (ret < 0)
2644cf2ddf1SOleksij Rempel return ret;
265ca3de46bSShawn Guo
266f085f672SAnson Huang regmap_read(map, soc_data->temp_data, &val);
26737713a1eSPhilipp Zabel
268abda7383SDaniel Lezcano if ((val & soc_data->temp_valid_mask) == 0)
269ca3de46bSShawn Guo return -EAGAIN;
270ca3de46bSShawn Guo
271f085f672SAnson Huang n_meas = (val & soc_data->temp_value_mask)
272f085f672SAnson Huang >> soc_data->temp_value_shift;
273ca3de46bSShawn Guo
274ae621557SLeonard Crestez /* See imx_init_calib() for formula derivation */
275f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D)
276f085f672SAnson Huang *temp = (n_meas - data->c1 + 25) * 1000;
277f085f672SAnson Huang else
278749e8be7SAnson Huang *temp = data->c2 - n_meas * data->c1;
279ca3de46bSShawn Guo
2803c94f17eSAnson Huang /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */
2813c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6Q) {
28230233a22SDaniel Lezcano if (data->alarm_temp == trips[IMX_TRIP_PASSIVE].temperature &&
28330233a22SDaniel Lezcano *temp >= trips[IMX_TRIP_PASSIVE].temperature)
28430233a22SDaniel Lezcano imx_set_alarm_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
28530233a22SDaniel Lezcano if (data->alarm_temp == trips[IMX_TRIP_CRITICAL].temperature &&
28630233a22SDaniel Lezcano *temp < trips[IMX_TRIP_PASSIVE].temperature) {
28730233a22SDaniel Lezcano imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
288dec07d39SDaniel Lezcano dev_dbg(data->dev, "thermal alarm off: T < %d\n",
28937713a1eSPhilipp Zabel data->alarm_temp / 1000);
29037713a1eSPhilipp Zabel }
2913c94f17eSAnson Huang }
29237713a1eSPhilipp Zabel
29337713a1eSPhilipp Zabel if (*temp != data->last_temp) {
294dec07d39SDaniel Lezcano dev_dbg(data->dev, "millicelsius: %d\n", *temp);
29537713a1eSPhilipp Zabel data->last_temp = *temp;
29637713a1eSPhilipp Zabel }
29737713a1eSPhilipp Zabel
29837713a1eSPhilipp Zabel /* Reenable alarm IRQ if temperature below alarm temperature */
29937713a1eSPhilipp Zabel if (!data->irq_enabled && *temp < data->alarm_temp) {
30037713a1eSPhilipp Zabel data->irq_enabled = true;
30137713a1eSPhilipp Zabel enable_irq(data->irq);
302ca3de46bSShawn Guo }
303ca3de46bSShawn Guo
3044cf2ddf1SOleksij Rempel pm_runtime_put(data->dev);
3054cf2ddf1SOleksij Rempel
306ca3de46bSShawn Guo return 0;
307ca3de46bSShawn Guo }
308ca3de46bSShawn Guo
imx_change_mode(struct thermal_zone_device * tz,enum thermal_device_mode mode)309f5e50bf4SAndrzej Pietrasiewicz static int imx_change_mode(struct thermal_zone_device *tz,
310ca3de46bSShawn Guo enum thermal_device_mode mode)
311ca3de46bSShawn Guo {
3125f68d078SDaniel Lezcano struct imx_thermal_data *data = thermal_zone_device_priv(tz);
313ca3de46bSShawn Guo
314ca3de46bSShawn Guo if (mode == THERMAL_DEVICE_ENABLED) {
3154cf2ddf1SOleksij Rempel pm_runtime_get(data->dev);
31637713a1eSPhilipp Zabel
31737713a1eSPhilipp Zabel if (!data->irq_enabled) {
31837713a1eSPhilipp Zabel data->irq_enabled = true;
31937713a1eSPhilipp Zabel enable_irq(data->irq);
32037713a1eSPhilipp Zabel }
321ca3de46bSShawn Guo } else {
3224cf2ddf1SOleksij Rempel pm_runtime_put(data->dev);
32337713a1eSPhilipp Zabel
32437713a1eSPhilipp Zabel if (data->irq_enabled) {
32537713a1eSPhilipp Zabel disable_irq(data->irq);
32637713a1eSPhilipp Zabel data->irq_enabled = false;
32737713a1eSPhilipp Zabel }
328ca3de46bSShawn Guo }
329ca3de46bSShawn Guo
330ca3de46bSShawn Guo return 0;
331ca3de46bSShawn Guo }
332ca3de46bSShawn Guo
imx_set_trip_temp(struct thermal_zone_device * tz,int trip_id,int temp)333ed4b51b8SDaniel Lezcano static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip_id,
33417e8351aSSascha Hauer int temp)
335017e5142SPhilipp Zabel {
3365f68d078SDaniel Lezcano struct imx_thermal_data *data = thermal_zone_device_priv(tz);
337ed4b51b8SDaniel Lezcano struct thermal_trip trip;
3384cf2ddf1SOleksij Rempel int ret;
3394cf2ddf1SOleksij Rempel
3404cf2ddf1SOleksij Rempel ret = pm_runtime_resume_and_get(data->dev);
3414cf2ddf1SOleksij Rempel if (ret < 0)
3424cf2ddf1SOleksij Rempel return ret;
343017e5142SPhilipp Zabel
344ed4b51b8SDaniel Lezcano ret = __thermal_zone_get_trip(tz, trip_id, &trip);
345ed4b51b8SDaniel Lezcano if (ret)
346ed4b51b8SDaniel Lezcano return ret;
347ed4b51b8SDaniel Lezcano
348a2291badSTim Harvey /* do not allow changing critical threshold */
349ed4b51b8SDaniel Lezcano if (trip.type == THERMAL_TRIP_CRITICAL)
350017e5142SPhilipp Zabel return -EPERM;
351017e5142SPhilipp Zabel
352a2291badSTim Harvey /* do not allow passive to be set higher than critical */
35330233a22SDaniel Lezcano if (temp < 0 || temp > trips[IMX_TRIP_CRITICAL].temperature)
354017e5142SPhilipp Zabel return -EINVAL;
355017e5142SPhilipp Zabel
35637713a1eSPhilipp Zabel imx_set_alarm_temp(data, temp);
35737713a1eSPhilipp Zabel
3584cf2ddf1SOleksij Rempel pm_runtime_put(data->dev);
3594cf2ddf1SOleksij Rempel
360ca3de46bSShawn Guo return 0;
361ca3de46bSShawn Guo }
362ca3de46bSShawn Guo
imx_bind(struct thermal_zone_device * tz,struct thermal_cooling_device * cdev)363ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz,
364ca3de46bSShawn Guo struct thermal_cooling_device *cdev)
365ca3de46bSShawn Guo {
366dec07d39SDaniel Lezcano return thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
367ca3de46bSShawn Guo THERMAL_NO_LIMIT,
3686cd9e9f6SKapileshwar Singh THERMAL_NO_LIMIT,
3696cd9e9f6SKapileshwar Singh THERMAL_WEIGHT_DEFAULT);
370ca3de46bSShawn Guo }
371ca3de46bSShawn Guo
imx_unbind(struct thermal_zone_device * tz,struct thermal_cooling_device * cdev)372ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz,
373ca3de46bSShawn Guo struct thermal_cooling_device *cdev)
374ca3de46bSShawn Guo {
375dec07d39SDaniel Lezcano return thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
376ca3de46bSShawn Guo }
377ca3de46bSShawn Guo
378cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = {
379ca3de46bSShawn Guo .bind = imx_bind,
380ca3de46bSShawn Guo .unbind = imx_unbind,
381ca3de46bSShawn Guo .get_temp = imx_get_temp,
382f5e50bf4SAndrzej Pietrasiewicz .change_mode = imx_change_mode,
383017e5142SPhilipp Zabel .set_trip_temp = imx_set_trip_temp,
384ca3de46bSShawn Guo };
385ca3de46bSShawn Guo
imx_init_calib(struct platform_device * pdev,u32 ocotp_ana1)386e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
387ca3de46bSShawn Guo {
388ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev);
3894e5f61caSUwe Kleine-König int n1;
390749e8be7SAnson Huang u64 temp64;
391ca3de46bSShawn Guo
392e4bb2240SUwe Kleine-König if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
393ca3de46bSShawn Guo dev_err(&pdev->dev, "invalid sensor calibration data\n");
394ca3de46bSShawn Guo return -EINVAL;
395ca3de46bSShawn Guo }
396ca3de46bSShawn Guo
397ca3de46bSShawn Guo /*
398f085f672SAnson Huang * On i.MX7D, we only use the calibration data at 25C to get the temp,
399f085f672SAnson Huang * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
400f085f672SAnson Huang */
401f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) {
402f085f672SAnson Huang data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
403f085f672SAnson Huang return 0;
404f085f672SAnson Huang }
405f085f672SAnson Huang
406f085f672SAnson Huang /*
407c5bbdb4bSUwe Kleine-König * The sensor is calibrated at 25 °C (aka T1) and the value measured
408c5bbdb4bSUwe Kleine-König * (aka N1) at this temperature is provided in bits [31:20] in the
409c5bbdb4bSUwe Kleine-König * i.MX's OCOTP value ANA1.
410c5bbdb4bSUwe Kleine-König * To find the actual temperature T, the following formula has to be used
411c5bbdb4bSUwe Kleine-König * when reading value n from the sensor:
412c5bbdb4bSUwe Kleine-König *
4134e5f61caSUwe Kleine-König * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
4144e5f61caSUwe Kleine-König * = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
4154e5f61caSUwe Kleine-König * = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
416c5bbdb4bSUwe Kleine-König * = c2 - c1 * N
417c5bbdb4bSUwe Kleine-König *
418c5bbdb4bSUwe Kleine-König * with
419c5bbdb4bSUwe Kleine-König *
4204e5f61caSUwe Kleine-König * T1' = 28.580661 °C
4214e5f61caSUwe Kleine-König * c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
4224e5f61caSUwe Kleine-König * c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
4234e5f61caSUwe Kleine-König * = T1' + N1 * c1
424ca3de46bSShawn Guo */
425e4bb2240SUwe Kleine-König n1 = ocotp_ana1 >> 20;
426ca3de46bSShawn Guo
4274e5f61caSUwe Kleine-König temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
428c5bbdb4bSUwe Kleine-König temp64 *= 1000; /* to get result in °mC */
4294e5f61caSUwe Kleine-König do_div(temp64, 15423 * n1 - 4148468);
430749e8be7SAnson Huang data->c1 = temp64;
4314e5f61caSUwe Kleine-König data->c2 = n1 * data->c1 + 28581;
432ca3de46bSShawn Guo
433ae621557SLeonard Crestez return 0;
434a2291badSTim Harvey }
435a2291badSTim Harvey
imx_init_temp_grade(struct platform_device * pdev,u32 ocotp_mem0)436e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
437ae621557SLeonard Crestez {
438ae621557SLeonard Crestez struct imx_thermal_data *data = platform_get_drvdata(pdev);
439ae621557SLeonard Crestez
440a2291badSTim Harvey /* The maximum die temp is specified by the Temperature Grade */
441e4bb2240SUwe Kleine-König switch ((ocotp_mem0 >> 6) & 0x3) {
442339d7492SUwe Kleine-König case 0: /* Commercial (0 to 95 °C) */
443a2291badSTim Harvey data->temp_grade = "Commercial";
444a2291badSTim Harvey data->temp_max = 95000;
445a2291badSTim Harvey break;
446339d7492SUwe Kleine-König case 1: /* Extended Commercial (-20 °C to 105 °C) */
447a2291badSTim Harvey data->temp_grade = "Extended Commercial";
448a2291badSTim Harvey data->temp_max = 105000;
449a2291badSTim Harvey break;
450339d7492SUwe Kleine-König case 2: /* Industrial (-40 °C to 105 °C) */
451a2291badSTim Harvey data->temp_grade = "Industrial";
452a2291badSTim Harvey data->temp_max = 105000;
453a2291badSTim Harvey break;
454339d7492SUwe Kleine-König case 3: /* Automotive (-40 °C to 125 °C) */
455a2291badSTim Harvey data->temp_grade = "Automotive";
456a2291badSTim Harvey data->temp_max = 125000;
457a2291badSTim Harvey break;
458a2291badSTim Harvey }
459017e5142SPhilipp Zabel
460017e5142SPhilipp Zabel /*
461339d7492SUwe Kleine-König * Set the critical trip point at 5 °C under max
462339d7492SUwe Kleine-König * Set the passive trip point at 10 °C under max (changeable via sysfs)
463017e5142SPhilipp Zabel */
46430233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature = data->temp_max - (1000 * 10);
46530233a22SDaniel Lezcano trips[IMX_TRIP_CRITICAL].temperature = data->temp_max - (1000 * 5);
466ae621557SLeonard Crestez }
467ae621557SLeonard Crestez
imx_init_from_tempmon_data(struct platform_device * pdev)468ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev)
469ae621557SLeonard Crestez {
470ae621557SLeonard Crestez struct regmap *map;
471ae621557SLeonard Crestez int ret;
472ae621557SLeonard Crestez u32 val;
473ae621557SLeonard Crestez
474ae621557SLeonard Crestez map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
475ae621557SLeonard Crestez "fsl,tempmon-data");
476ae621557SLeonard Crestez if (IS_ERR(map)) {
477ae621557SLeonard Crestez ret = PTR_ERR(map);
478ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
479ae621557SLeonard Crestez return ret;
480ae621557SLeonard Crestez }
481ae621557SLeonard Crestez
482ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_ANA1, &val);
483ae621557SLeonard Crestez if (ret) {
484ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
485ae621557SLeonard Crestez return ret;
486ae621557SLeonard Crestez }
487ae621557SLeonard Crestez ret = imx_init_calib(pdev, val);
488ae621557SLeonard Crestez if (ret)
489ae621557SLeonard Crestez return ret;
490ae621557SLeonard Crestez
491ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_MEM0, &val);
492ae621557SLeonard Crestez if (ret) {
493ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
494ae621557SLeonard Crestez return ret;
495ae621557SLeonard Crestez }
496ae621557SLeonard Crestez imx_init_temp_grade(pdev, val);
497ae621557SLeonard Crestez
498ae621557SLeonard Crestez return 0;
499ae621557SLeonard Crestez }
500ae621557SLeonard Crestez
imx_init_from_nvmem_cells(struct platform_device * pdev)501ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev)
502ae621557SLeonard Crestez {
503ae621557SLeonard Crestez int ret;
504ae621557SLeonard Crestez u32 val;
505ae621557SLeonard Crestez
506ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
507ae621557SLeonard Crestez if (ret)
508ae621557SLeonard Crestez return ret;
509be926ceeSJean-Christophe Dubois
510be926ceeSJean-Christophe Dubois ret = imx_init_calib(pdev, val);
511be926ceeSJean-Christophe Dubois if (ret)
512be926ceeSJean-Christophe Dubois return ret;
513ae621557SLeonard Crestez
514ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
515ae621557SLeonard Crestez if (ret)
516ae621557SLeonard Crestez return ret;
517ae621557SLeonard Crestez imx_init_temp_grade(pdev, val);
518017e5142SPhilipp Zabel
519ca3de46bSShawn Guo return 0;
520ca3de46bSShawn Guo }
521ca3de46bSShawn Guo
imx_thermal_alarm_irq(int irq,void * dev)52237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
52337713a1eSPhilipp Zabel {
52437713a1eSPhilipp Zabel struct imx_thermal_data *data = dev;
52537713a1eSPhilipp Zabel
52637713a1eSPhilipp Zabel disable_irq_nosync(irq);
52737713a1eSPhilipp Zabel data->irq_enabled = false;
52837713a1eSPhilipp Zabel
52937713a1eSPhilipp Zabel return IRQ_WAKE_THREAD;
53037713a1eSPhilipp Zabel }
53137713a1eSPhilipp Zabel
imx_thermal_alarm_irq_thread(int irq,void * dev)53237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
53337713a1eSPhilipp Zabel {
53437713a1eSPhilipp Zabel struct imx_thermal_data *data = dev;
53537713a1eSPhilipp Zabel
536dec07d39SDaniel Lezcano dev_dbg(data->dev, "THERMAL ALARM: T > %d\n", data->alarm_temp / 1000);
53737713a1eSPhilipp Zabel
5380e70f466SSrinivas Pandruvada thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
53937713a1eSPhilipp Zabel
54037713a1eSPhilipp Zabel return IRQ_HANDLED;
54137713a1eSPhilipp Zabel }
54237713a1eSPhilipp Zabel
5433c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = {
5443c94f17eSAnson Huang { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
5453c94f17eSAnson Huang { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
546f085f672SAnson Huang { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
5473c94f17eSAnson Huang { /* end */ }
5483c94f17eSAnson Huang };
5493c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
5503c94f17eSAnson Huang
551c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ
552a1d00154SBastian Stender /*
553a1d00154SBastian Stender * Create cooling device in case no #cooling-cells property is available in
554a1d00154SBastian Stender * CPU node
555a1d00154SBastian Stender */
imx_thermal_register_legacy_cooling(struct imx_thermal_data * data)556a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
557a1d00154SBastian Stender {
558c589c566SAnson Huang struct device_node *np;
559b45fd13bSAnson Huang int ret = 0;
560a1d00154SBastian Stender
561c589c566SAnson Huang data->policy = cpufreq_cpu_get(0);
562c589c566SAnson Huang if (!data->policy) {
563c589c566SAnson Huang pr_debug("%s: CPUFreq policy not found\n", __func__);
564c589c566SAnson Huang return -EPROBE_DEFER;
565c589c566SAnson Huang }
566c589c566SAnson Huang
567c589c566SAnson Huang np = of_get_cpu_node(data->policy->cpu, NULL);
568c589c566SAnson Huang
56986df7d19SRob Herring if (!np || !of_property_present(np, "#cooling-cells")) {
570a1d00154SBastian Stender data->cdev = cpufreq_cooling_register(data->policy);
571a1d00154SBastian Stender if (IS_ERR(data->cdev)) {
572a1d00154SBastian Stender ret = PTR_ERR(data->cdev);
573a1d00154SBastian Stender cpufreq_cpu_put(data->policy);
574a1d00154SBastian Stender }
575a1d00154SBastian Stender }
576a1d00154SBastian Stender
577b45fd13bSAnson Huang of_node_put(np);
578b45fd13bSAnson Huang
579b45fd13bSAnson Huang return ret;
580a1d00154SBastian Stender }
581a1d00154SBastian Stender
imx_thermal_unregister_legacy_cooling(struct imx_thermal_data * data)582c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
583c589c566SAnson Huang {
584c589c566SAnson Huang cpufreq_cooling_unregister(data->cdev);
585c589c566SAnson Huang cpufreq_cpu_put(data->policy);
586c589c566SAnson Huang }
587c589c566SAnson Huang
588c589c566SAnson Huang #else
589c589c566SAnson Huang
imx_thermal_register_legacy_cooling(struct imx_thermal_data * data)590c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
591c589c566SAnson Huang {
592c589c566SAnson Huang return 0;
593c589c566SAnson Huang }
594c589c566SAnson Huang
imx_thermal_unregister_legacy_cooling(struct imx_thermal_data * data)595c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
596c589c566SAnson Huang {
597c589c566SAnson Huang }
598c589c566SAnson Huang #endif
599c589c566SAnson Huang
imx_thermal_probe(struct platform_device * pdev)600ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev)
601ca3de46bSShawn Guo {
602ca3de46bSShawn Guo struct imx_thermal_data *data;
603ca3de46bSShawn Guo struct regmap *map;
60437713a1eSPhilipp Zabel int measure_freq;
605ca3de46bSShawn Guo int ret;
606ca3de46bSShawn Guo
607ca3de46bSShawn Guo data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
608ca3de46bSShawn Guo if (!data)
609ca3de46bSShawn Guo return -ENOMEM;
610ca3de46bSShawn Guo
6114cf2ddf1SOleksij Rempel data->dev = &pdev->dev;
6124cf2ddf1SOleksij Rempel
613ca3de46bSShawn Guo map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
614ca3de46bSShawn Guo if (IS_ERR(map)) {
615ca3de46bSShawn Guo ret = PTR_ERR(map);
616ca3de46bSShawn Guo dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
617ca3de46bSShawn Guo return ret;
618ca3de46bSShawn Guo }
619ca3de46bSShawn Guo data->tempmon = map;
620ca3de46bSShawn Guo
621829bc78aSCorentin LABBE data->socdata = of_device_get_match_data(&pdev->dev);
6228b051ec3SShailendra Verma if (!data->socdata) {
6238b051ec3SShailendra Verma dev_err(&pdev->dev, "no device match found\n");
6248b051ec3SShailendra Verma return -ENODEV;
6258b051ec3SShailendra Verma }
6263c94f17eSAnson Huang
6273c94f17eSAnson Huang /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
6283c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) {
629f085f672SAnson Huang regmap_write(map, IMX6_MISC1 + REG_CLR,
630f085f672SAnson Huang IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
631f085f672SAnson Huang | IMX6_MISC1_IRQ_TEMPPANIC);
6323c94f17eSAnson Huang /*
6333c94f17eSAnson Huang * reset value of LOW ALARM is incorrect, set it to lowest
6343c94f17eSAnson Huang * value to avoid false trigger of low alarm.
6353c94f17eSAnson Huang */
636f085f672SAnson Huang regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
637f085f672SAnson Huang data->socdata->low_alarm_mask);
6383c94f17eSAnson Huang }
6393c94f17eSAnson Huang
64037713a1eSPhilipp Zabel data->irq = platform_get_irq(pdev, 0);
64137713a1eSPhilipp Zabel if (data->irq < 0)
64237713a1eSPhilipp Zabel return data->irq;
64337713a1eSPhilipp Zabel
644ca3de46bSShawn Guo platform_set_drvdata(pdev, data);
645ca3de46bSShawn Guo
64686df7d19SRob Herring if (of_property_present(pdev->dev.of_node, "nvmem-cells")) {
647ae621557SLeonard Crestez ret = imx_init_from_nvmem_cells(pdev);
6485f3c0200SAnson Huang if (ret)
6495f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret,
6505f3c0200SAnson Huang "failed to init from nvmem\n");
651ae621557SLeonard Crestez } else {
652ae621557SLeonard Crestez ret = imx_init_from_tempmon_data(pdev);
653ae621557SLeonard Crestez if (ret) {
654337a4aecSAnson Huang dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
655ae621557SLeonard Crestez return ret;
656ae621557SLeonard Crestez }
657ca3de46bSShawn Guo }
658ca3de46bSShawn Guo
659ca3de46bSShawn Guo /* Make sure sensor is in known good state for measurements */
660f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
661f085f672SAnson Huang data->socdata->power_down_mask);
662f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
663f085f672SAnson Huang data->socdata->measure_temp_mask);
664f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
665f085f672SAnson Huang data->socdata->measure_freq_mask);
666f085f672SAnson Huang if (data->socdata->version != TEMPMON_IMX7D)
667f085f672SAnson Huang regmap_write(map, IMX6_MISC0 + REG_SET,
668f085f672SAnson Huang IMX6_MISC0_REFTOP_SELBIASOFF);
669f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
670f085f672SAnson Huang data->socdata->power_down_mask);
671ca3de46bSShawn Guo
672a1d00154SBastian Stender ret = imx_thermal_register_legacy_cooling(data);
6735f3c0200SAnson Huang if (ret)
6745f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret,
6755f3c0200SAnson Huang "failed to register cpufreq cooling device\n");
676ca3de46bSShawn Guo
67790a21ff5SHeiner Kallweit data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
67890a21ff5SHeiner Kallweit if (IS_ERR(data->thermal_clk)) {
67990a21ff5SHeiner Kallweit ret = PTR_ERR(data->thermal_clk);
68090a21ff5SHeiner Kallweit if (ret != -EPROBE_DEFER)
68190a21ff5SHeiner Kallweit dev_err(&pdev->dev,
68290a21ff5SHeiner Kallweit "failed to get thermal clk: %d\n", ret);
683c589c566SAnson Huang goto legacy_cleanup;
68490a21ff5SHeiner Kallweit }
68590a21ff5SHeiner Kallweit
68690a21ff5SHeiner Kallweit /*
68790a21ff5SHeiner Kallweit * Thermal sensor needs clk on to get correct value, normally
68890a21ff5SHeiner Kallweit * we should enable its clk before taking measurement and disable
68990a21ff5SHeiner Kallweit * clk after measurement is done, but if alarm function is enabled,
69090a21ff5SHeiner Kallweit * hardware will auto measure the temperature periodically, so we
69190a21ff5SHeiner Kallweit * need to keep the clk always on for alarm function.
69290a21ff5SHeiner Kallweit */
69390a21ff5SHeiner Kallweit ret = clk_prepare_enable(data->thermal_clk);
69490a21ff5SHeiner Kallweit if (ret) {
69590a21ff5SHeiner Kallweit dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
696c589c566SAnson Huang goto legacy_cleanup;
69790a21ff5SHeiner Kallweit }
69890a21ff5SHeiner Kallweit
69930233a22SDaniel Lezcano data->tz = thermal_zone_device_register_with_trips("imx_thermal_zone",
70030233a22SDaniel Lezcano trips,
70130233a22SDaniel Lezcano ARRAY_SIZE(trips),
702017e5142SPhilipp Zabel BIT(IMX_TRIP_PASSIVE), data,
703ca3de46bSShawn Guo &imx_tz_ops, NULL,
704ca3de46bSShawn Guo IMX_PASSIVE_DELAY,
705ca3de46bSShawn Guo IMX_POLLING_DELAY);
706ca3de46bSShawn Guo if (IS_ERR(data->tz)) {
707ca3de46bSShawn Guo ret = PTR_ERR(data->tz);
708ca3de46bSShawn Guo dev_err(&pdev->dev,
709ca3de46bSShawn Guo "failed to register thermal zone device %d\n", ret);
710b6ad3981SAnson Huang goto clk_disable;
711ca3de46bSShawn Guo }
712ca3de46bSShawn Guo
713a2291badSTim Harvey dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
714a2291badSTim Harvey " critical:%dC passive:%dC\n", data->temp_grade,
71530233a22SDaniel Lezcano data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000,
71630233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature / 1000);
717a2291badSTim Harvey
71837713a1eSPhilipp Zabel /* Enable measurements at ~ 10 Hz */
719f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
720f085f672SAnson Huang data->socdata->measure_freq_mask);
72137713a1eSPhilipp Zabel measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
722f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
723f085f672SAnson Huang measure_freq << data->socdata->measure_freq_shift);
72430233a22SDaniel Lezcano imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
7253c94f17eSAnson Huang
7263c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX)
72730233a22SDaniel Lezcano imx_set_panic_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
7283c94f17eSAnson Huang
729f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
730f085f672SAnson Huang data->socdata->power_down_mask);
731f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
732f085f672SAnson Huang data->socdata->measure_temp_mask);
7334cf2ddf1SOleksij Rempel /* After power up, we need a delay before first access can be done. */
7344cf2ddf1SOleksij Rempel usleep_range(20, 50);
7354cf2ddf1SOleksij Rempel
7364cf2ddf1SOleksij Rempel /* the core was configured and enabled just before */
7374cf2ddf1SOleksij Rempel pm_runtime_set_active(&pdev->dev);
7384cf2ddf1SOleksij Rempel pm_runtime_enable(data->dev);
7394cf2ddf1SOleksij Rempel
7404cf2ddf1SOleksij Rempel ret = pm_runtime_resume_and_get(data->dev);
7414cf2ddf1SOleksij Rempel if (ret < 0)
7424cf2ddf1SOleksij Rempel goto disable_runtime_pm;
74337713a1eSPhilipp Zabel
744cf1ba1d7SMikhail Lappo data->irq_enabled = true;
7457f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_enable(data->tz);
7467f4957beSAndrzej Pietrasiewicz if (ret)
7477f4957beSAndrzej Pietrasiewicz goto thermal_zone_unregister;
748cf1ba1d7SMikhail Lappo
74984866ee5SBai Ping ret = devm_request_threaded_irq(&pdev->dev, data->irq,
75084866ee5SBai Ping imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
75184866ee5SBai Ping 0, "imx_thermal", data);
75284866ee5SBai Ping if (ret < 0) {
75384866ee5SBai Ping dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
754b6ad3981SAnson Huang goto thermal_zone_unregister;
75584866ee5SBai Ping }
75684866ee5SBai Ping
7574cf2ddf1SOleksij Rempel pm_runtime_put(data->dev);
7584cf2ddf1SOleksij Rempel
759ca3de46bSShawn Guo return 0;
760b6ad3981SAnson Huang
761b6ad3981SAnson Huang thermal_zone_unregister:
762b6ad3981SAnson Huang thermal_zone_device_unregister(data->tz);
7634cf2ddf1SOleksij Rempel disable_runtime_pm:
7644cf2ddf1SOleksij Rempel pm_runtime_put_noidle(data->dev);
7654cf2ddf1SOleksij Rempel pm_runtime_disable(data->dev);
766b6ad3981SAnson Huang clk_disable:
767b6ad3981SAnson Huang clk_disable_unprepare(data->thermal_clk);
768c589c566SAnson Huang legacy_cleanup:
769c589c566SAnson Huang imx_thermal_unregister_legacy_cooling(data);
770b6ad3981SAnson Huang
771b6ad3981SAnson Huang return ret;
772ca3de46bSShawn Guo }
773ca3de46bSShawn Guo
imx_thermal_remove(struct platform_device * pdev)774ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev)
775ca3de46bSShawn Guo {
776ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev);
77737713a1eSPhilipp Zabel
7784cf2ddf1SOleksij Rempel pm_runtime_put_noidle(data->dev);
7794cf2ddf1SOleksij Rempel pm_runtime_disable(data->dev);
780ca3de46bSShawn Guo
781ca3de46bSShawn Guo thermal_zone_device_unregister(data->tz);
7829db11010SAnson Huang imx_thermal_unregister_legacy_cooling(data);
783ca3de46bSShawn Guo
784ca3de46bSShawn Guo return 0;
785ca3de46bSShawn Guo }
786ca3de46bSShawn Guo
imx_thermal_suspend(struct device * dev)787b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev)
788ca3de46bSShawn Guo {
789ca3de46bSShawn Guo struct imx_thermal_data *data = dev_get_drvdata(dev);
7907f4957beSAndrzej Pietrasiewicz int ret;
791ca3de46bSShawn Guo
792ca3de46bSShawn Guo /*
793b46cce59SAnson Huang * Need to disable thermal sensor, otherwise, when thermal core
794b46cce59SAnson Huang * try to get temperature before thermal sensor resume, a wrong
795b46cce59SAnson Huang * temperature will be read as the thermal sensor is powered
796f5e50bf4SAndrzej Pietrasiewicz * down. This is done in change_mode() operation called from
7977f4957beSAndrzej Pietrasiewicz * thermal_zone_device_disable()
798ca3de46bSShawn Guo */
7997f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_disable(data->tz);
8007f4957beSAndrzej Pietrasiewicz if (ret)
8017f4957beSAndrzej Pietrasiewicz return ret;
802ca3de46bSShawn Guo
8034cf2ddf1SOleksij Rempel return pm_runtime_force_suspend(data->dev);
804ca3de46bSShawn Guo }
805ca3de46bSShawn Guo
imx_thermal_resume(struct device * dev)806b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev)
807ca3de46bSShawn Guo {
808b46cce59SAnson Huang struct imx_thermal_data *data = dev_get_drvdata(dev);
809e3bdc8d7SArvind Yadav int ret;
810b46cce59SAnson Huang
8114cf2ddf1SOleksij Rempel ret = pm_runtime_force_resume(data->dev);
812e3bdc8d7SArvind Yadav if (ret)
813e3bdc8d7SArvind Yadav return ret;
814b46cce59SAnson Huang /* Enabled thermal sensor after resume */
8154cf2ddf1SOleksij Rempel return thermal_zone_device_enable(data->tz);
8164cf2ddf1SOleksij Rempel }
8174cf2ddf1SOleksij Rempel
imx_thermal_runtime_suspend(struct device * dev)8184cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev)
8194cf2ddf1SOleksij Rempel {
8204cf2ddf1SOleksij Rempel struct imx_thermal_data *data = dev_get_drvdata(dev);
8214cf2ddf1SOleksij Rempel const struct thermal_soc_data *socdata = data->socdata;
8224cf2ddf1SOleksij Rempel struct regmap *map = data->tempmon;
8234cf2ddf1SOleksij Rempel int ret;
8244cf2ddf1SOleksij Rempel
8254cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8264cf2ddf1SOleksij Rempel socdata->measure_temp_mask);
8277f4957beSAndrzej Pietrasiewicz if (ret)
8287f4957beSAndrzej Pietrasiewicz return ret;
829b46cce59SAnson Huang
8304cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8314cf2ddf1SOleksij Rempel socdata->power_down_mask);
8324cf2ddf1SOleksij Rempel if (ret)
8334cf2ddf1SOleksij Rempel return ret;
8344cf2ddf1SOleksij Rempel
8354cf2ddf1SOleksij Rempel clk_disable_unprepare(data->thermal_clk);
8364cf2ddf1SOleksij Rempel
837ca3de46bSShawn Guo return 0;
838ca3de46bSShawn Guo }
839ca3de46bSShawn Guo
imx_thermal_runtime_resume(struct device * dev)8404cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_resume(struct device *dev)
8414cf2ddf1SOleksij Rempel {
8424cf2ddf1SOleksij Rempel struct imx_thermal_data *data = dev_get_drvdata(dev);
8434cf2ddf1SOleksij Rempel const struct thermal_soc_data *socdata = data->socdata;
8444cf2ddf1SOleksij Rempel struct regmap *map = data->tempmon;
8454cf2ddf1SOleksij Rempel int ret;
8464cf2ddf1SOleksij Rempel
8474cf2ddf1SOleksij Rempel ret = clk_prepare_enable(data->thermal_clk);
8484cf2ddf1SOleksij Rempel if (ret)
8494cf2ddf1SOleksij Rempel return ret;
8504cf2ddf1SOleksij Rempel
8514cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
8524cf2ddf1SOleksij Rempel socdata->power_down_mask);
8534cf2ddf1SOleksij Rempel if (ret)
8544cf2ddf1SOleksij Rempel return ret;
8554cf2ddf1SOleksij Rempel
8564cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
8574cf2ddf1SOleksij Rempel socdata->measure_temp_mask);
8584cf2ddf1SOleksij Rempel if (ret)
8594cf2ddf1SOleksij Rempel return ret;
8604cf2ddf1SOleksij Rempel
8614cf2ddf1SOleksij Rempel /*
8624cf2ddf1SOleksij Rempel * According to the temp sensor designers, it may require up to ~17us
8634cf2ddf1SOleksij Rempel * to complete a measurement.
8644cf2ddf1SOleksij Rempel */
8654cf2ddf1SOleksij Rempel usleep_range(20, 50);
8664cf2ddf1SOleksij Rempel
8674cf2ddf1SOleksij Rempel return 0;
8684cf2ddf1SOleksij Rempel }
8694cf2ddf1SOleksij Rempel
8704cf2ddf1SOleksij Rempel static const struct dev_pm_ops imx_thermal_pm_ops = {
8714cf2ddf1SOleksij Rempel SET_SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume)
8724cf2ddf1SOleksij Rempel SET_RUNTIME_PM_OPS(imx_thermal_runtime_suspend,
8734cf2ddf1SOleksij Rempel imx_thermal_runtime_resume, NULL)
8744cf2ddf1SOleksij Rempel };
875ca3de46bSShawn Guo
876ca3de46bSShawn Guo static struct platform_driver imx_thermal = {
877ca3de46bSShawn Guo .driver = {
878ca3de46bSShawn Guo .name = "imx_thermal",
879ca3de46bSShawn Guo .pm = &imx_thermal_pm_ops,
880ca3de46bSShawn Guo .of_match_table = of_imx_thermal_match,
881ca3de46bSShawn Guo },
882ca3de46bSShawn Guo .probe = imx_thermal_probe,
883ca3de46bSShawn Guo .remove = imx_thermal_remove,
884ca3de46bSShawn Guo };
885ca3de46bSShawn Guo module_platform_driver(imx_thermal);
886ca3de46bSShawn Guo
887ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc.");
888ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
889ca3de46bSShawn Guo MODULE_LICENSE("GPL v2");
890ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal");
891