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 10329fe7b1SAnson Huang #include <linux/clk.h> 114d753aa7SViresh Kumar #include <linux/cpufreq.h> 12ca3de46bSShawn Guo #include <linux/cpu_cooling.h> 13ca3de46bSShawn Guo #include <linux/delay.h> 14ca3de46bSShawn Guo #include <linux/device.h> 15ca3de46bSShawn Guo #include <linux/init.h> 1637713a1eSPhilipp Zabel #include <linux/interrupt.h> 17ca3de46bSShawn Guo #include <linux/io.h> 18ca3de46bSShawn Guo #include <linux/kernel.h> 19ca3de46bSShawn Guo #include <linux/mfd/syscon.h> 20ca3de46bSShawn Guo #include <linux/module.h> 21ca3de46bSShawn Guo #include <linux/of.h> 223c94f17eSAnson Huang #include <linux/of_device.h> 23ca3de46bSShawn Guo #include <linux/platform_device.h> 24ca3de46bSShawn Guo #include <linux/regmap.h> 25ca3de46bSShawn Guo #include <linux/slab.h> 26ca3de46bSShawn Guo #include <linux/thermal.h> 27ca3de46bSShawn Guo #include <linux/types.h> 28ae621557SLeonard Crestez #include <linux/nvmem-consumer.h> 29ca3de46bSShawn Guo 30ca3de46bSShawn Guo #define REG_SET 0x4 31ca3de46bSShawn Guo #define REG_CLR 0x8 32ca3de46bSShawn Guo #define REG_TOG 0xc 33ca3de46bSShawn Guo 34f085f672SAnson Huang /* i.MX6 specific */ 35f085f672SAnson Huang #define IMX6_MISC0 0x0150 36f085f672SAnson Huang #define IMX6_MISC0_REFTOP_SELBIASOFF (1 << 3) 37f085f672SAnson Huang #define IMX6_MISC1 0x0160 38f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPHIGH (1 << 29) 393c94f17eSAnson Huang /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */ 40f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPLOW (1 << 28) 41f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPPANIC (1 << 27) 42ca3de46bSShawn Guo 43f085f672SAnson Huang #define IMX6_TEMPSENSE0 0x0180 44f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT 20 45f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_MASK (0xfff << 20) 46f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT 8 47f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_MASK (0xfff << 8) 48f085f672SAnson Huang #define IMX6_TEMPSENSE0_FINISHED (1 << 2) 49f085f672SAnson Huang #define IMX6_TEMPSENSE0_MEASURE_TEMP (1 << 1) 50f085f672SAnson Huang #define IMX6_TEMPSENSE0_POWER_DOWN (1 << 0) 51ca3de46bSShawn Guo 52f085f672SAnson Huang #define IMX6_TEMPSENSE1 0x0190 53f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ 0xffff 54f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT 0 55ca3de46bSShawn Guo 56a2291badSTim Harvey #define OCOTP_MEM0 0x0480 57ca3de46bSShawn Guo #define OCOTP_ANA1 0x04e0 58ca3de46bSShawn Guo 59f085f672SAnson Huang /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */ 60f085f672SAnson Huang #define IMX6_TEMPSENSE2 0x0290 61f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT 0 62f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_MASK 0xfff 63f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT 16 64f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000 65f085f672SAnson Huang 66f085f672SAnson Huang /* i.MX7 specific */ 67f085f672SAnson Huang #define IMX7_ANADIG_DIGPROG 0x800 68f085f672SAnson Huang #define IMX7_TEMPSENSE0 0x300 69f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT 18 70f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_MASK (0x1ff << 18) 71f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT 9 72f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_MASK (0x1ff << 9) 73f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT 0 74f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_MASK 0x1ff 75f085f672SAnson Huang 76f085f672SAnson Huang #define IMX7_TEMPSENSE1 0x310 77f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT 16 78f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK (0xffff << 16) 79f085f672SAnson Huang #define IMX7_TEMPSENSE1_FINISHED (1 << 11) 80f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_TEMP (1 << 10) 81f085f672SAnson Huang #define IMX7_TEMPSENSE1_POWER_DOWN (1 << 9) 82f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT 0 83f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_MASK 0x1ff 84f085f672SAnson Huang 85ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */ 86ca3de46bSShawn Guo enum imx_thermal_trip { 87ca3de46bSShawn Guo IMX_TRIP_PASSIVE, 88ca3de46bSShawn Guo IMX_TRIP_CRITICAL, 89ca3de46bSShawn Guo IMX_TRIP_NUM, 90ca3de46bSShawn Guo }; 91ca3de46bSShawn Guo 92ca3de46bSShawn Guo #define IMX_POLLING_DELAY 2000 /* millisecond */ 93ca3de46bSShawn Guo #define IMX_PASSIVE_DELAY 1000 94ca3de46bSShawn Guo 953c94f17eSAnson Huang #define TEMPMON_IMX6Q 1 963c94f17eSAnson Huang #define TEMPMON_IMX6SX 2 97f085f672SAnson Huang #define TEMPMON_IMX7D 3 983c94f17eSAnson Huang 993c94f17eSAnson Huang struct thermal_soc_data { 1003c94f17eSAnson Huang u32 version; 101f085f672SAnson Huang 102f085f672SAnson Huang u32 sensor_ctrl; 103f085f672SAnson Huang u32 power_down_mask; 104f085f672SAnson Huang u32 measure_temp_mask; 105f085f672SAnson Huang 106f085f672SAnson Huang u32 measure_freq_ctrl; 107f085f672SAnson Huang u32 measure_freq_mask; 108f085f672SAnson Huang u32 measure_freq_shift; 109f085f672SAnson Huang 110f085f672SAnson Huang u32 temp_data; 111f085f672SAnson Huang u32 temp_value_mask; 112f085f672SAnson Huang u32 temp_value_shift; 113f085f672SAnson Huang u32 temp_valid_mask; 114f085f672SAnson Huang 115f085f672SAnson Huang u32 panic_alarm_ctrl; 116f085f672SAnson Huang u32 panic_alarm_mask; 117f085f672SAnson Huang u32 panic_alarm_shift; 118f085f672SAnson Huang 119f085f672SAnson Huang u32 high_alarm_ctrl; 120f085f672SAnson Huang u32 high_alarm_mask; 121f085f672SAnson Huang u32 high_alarm_shift; 122f085f672SAnson Huang 123f085f672SAnson Huang u32 low_alarm_ctrl; 124f085f672SAnson Huang u32 low_alarm_mask; 125f085f672SAnson Huang u32 low_alarm_shift; 1263c94f17eSAnson Huang }; 1273c94f17eSAnson Huang 1283c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6q_data = { 1293c94f17eSAnson Huang .version = TEMPMON_IMX6Q, 130f085f672SAnson Huang 131f085f672SAnson Huang .sensor_ctrl = IMX6_TEMPSENSE0, 132f085f672SAnson Huang .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, 133f085f672SAnson Huang .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, 134f085f672SAnson Huang 135f085f672SAnson Huang .measure_freq_ctrl = IMX6_TEMPSENSE1, 136f085f672SAnson Huang .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, 137f085f672SAnson Huang .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, 138f085f672SAnson Huang 139f085f672SAnson Huang .temp_data = IMX6_TEMPSENSE0, 140f085f672SAnson Huang .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, 141f085f672SAnson Huang .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, 142f085f672SAnson Huang .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, 143f085f672SAnson Huang 144f085f672SAnson Huang .high_alarm_ctrl = IMX6_TEMPSENSE0, 145f085f672SAnson Huang .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, 146f085f672SAnson Huang .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, 1473c94f17eSAnson Huang }; 1483c94f17eSAnson Huang 1493c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6sx_data = { 1503c94f17eSAnson Huang .version = TEMPMON_IMX6SX, 151f085f672SAnson Huang 152f085f672SAnson Huang .sensor_ctrl = IMX6_TEMPSENSE0, 153f085f672SAnson Huang .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, 154f085f672SAnson Huang .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, 155f085f672SAnson Huang 156f085f672SAnson Huang .measure_freq_ctrl = IMX6_TEMPSENSE1, 157f085f672SAnson Huang .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, 158f085f672SAnson Huang .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, 159f085f672SAnson Huang 160f085f672SAnson Huang .temp_data = IMX6_TEMPSENSE0, 161f085f672SAnson Huang .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, 162f085f672SAnson Huang .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, 163f085f672SAnson Huang .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, 164f085f672SAnson Huang 165f085f672SAnson Huang .high_alarm_ctrl = IMX6_TEMPSENSE0, 166f085f672SAnson Huang .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, 167f085f672SAnson Huang .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, 168f085f672SAnson Huang 169f085f672SAnson Huang .panic_alarm_ctrl = IMX6_TEMPSENSE2, 170f085f672SAnson Huang .panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK, 171f085f672SAnson Huang .panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT, 172f085f672SAnson Huang 173f085f672SAnson Huang .low_alarm_ctrl = IMX6_TEMPSENSE2, 174f085f672SAnson Huang .low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK, 175f085f672SAnson Huang .low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT, 176f085f672SAnson Huang }; 177f085f672SAnson Huang 178f085f672SAnson Huang static struct thermal_soc_data thermal_imx7d_data = { 179f085f672SAnson Huang .version = TEMPMON_IMX7D, 180f085f672SAnson Huang 181f085f672SAnson Huang .sensor_ctrl = IMX7_TEMPSENSE1, 182f085f672SAnson Huang .power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN, 183f085f672SAnson Huang .measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP, 184f085f672SAnson Huang 185f085f672SAnson Huang .measure_freq_ctrl = IMX7_TEMPSENSE1, 186f085f672SAnson Huang .measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT, 187f085f672SAnson Huang .measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK, 188f085f672SAnson Huang 189f085f672SAnson Huang .temp_data = IMX7_TEMPSENSE1, 190f085f672SAnson Huang .temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK, 191f085f672SAnson Huang .temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT, 192f085f672SAnson Huang .temp_valid_mask = IMX7_TEMPSENSE1_FINISHED, 193f085f672SAnson Huang 194f085f672SAnson Huang .panic_alarm_ctrl = IMX7_TEMPSENSE1, 195f085f672SAnson Huang .panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK, 196f085f672SAnson Huang .panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT, 197f085f672SAnson Huang 198f085f672SAnson Huang .high_alarm_ctrl = IMX7_TEMPSENSE0, 199f085f672SAnson Huang .high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK, 200f085f672SAnson Huang .high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT, 201f085f672SAnson Huang 202f085f672SAnson Huang .low_alarm_ctrl = IMX7_TEMPSENSE0, 203f085f672SAnson Huang .low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK, 204f085f672SAnson Huang .low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT, 2053c94f17eSAnson Huang }; 2063c94f17eSAnson Huang 207ca3de46bSShawn Guo struct imx_thermal_data { 2084d753aa7SViresh Kumar struct cpufreq_policy *policy; 209ca3de46bSShawn Guo struct thermal_zone_device *tz; 210ca3de46bSShawn Guo struct thermal_cooling_device *cdev; 211ca3de46bSShawn Guo enum thermal_device_mode mode; 212ca3de46bSShawn Guo struct regmap *tempmon; 213ae621557SLeonard Crestez u32 c1, c2; /* See formula in imx_init_calib() */ 21417e8351aSSascha Hauer int temp_passive; 21517e8351aSSascha Hauer int temp_critical; 216a2291badSTim Harvey int temp_max; 21717e8351aSSascha Hauer int alarm_temp; 21817e8351aSSascha Hauer int last_temp; 21937713a1eSPhilipp Zabel bool irq_enabled; 22037713a1eSPhilipp Zabel int irq; 221329fe7b1SAnson Huang struct clk *thermal_clk; 2223c94f17eSAnson Huang const struct thermal_soc_data *socdata; 223a2291badSTim Harvey const char *temp_grade; 224ca3de46bSShawn Guo }; 225ca3de46bSShawn Guo 2263c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data, 22717e8351aSSascha Hauer int panic_temp) 2283c94f17eSAnson Huang { 229f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 2303c94f17eSAnson Huang struct regmap *map = data->tempmon; 2313c94f17eSAnson Huang int critical_value; 2323c94f17eSAnson Huang 2333c94f17eSAnson Huang critical_value = (data->c2 - panic_temp) / data->c1; 234f085f672SAnson Huang 235f085f672SAnson Huang regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR, 236f085f672SAnson Huang soc_data->panic_alarm_mask); 237f085f672SAnson Huang regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET, 238f085f672SAnson Huang critical_value << soc_data->panic_alarm_shift); 2393c94f17eSAnson Huang } 2403c94f17eSAnson Huang 24137713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data, 24217e8351aSSascha Hauer int alarm_temp) 24337713a1eSPhilipp Zabel { 24437713a1eSPhilipp Zabel struct regmap *map = data->tempmon; 245f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 24637713a1eSPhilipp Zabel int alarm_value; 24737713a1eSPhilipp Zabel 24837713a1eSPhilipp Zabel data->alarm_temp = alarm_temp; 249f085f672SAnson Huang 250f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) 251f085f672SAnson Huang alarm_value = alarm_temp / 1000 + data->c1 - 25; 252f085f672SAnson Huang else 253749e8be7SAnson Huang alarm_value = (data->c2 - alarm_temp) / data->c1; 254f085f672SAnson Huang 255f085f672SAnson Huang regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR, 256f085f672SAnson Huang soc_data->high_alarm_mask); 257f085f672SAnson Huang regmap_write(map, soc_data->high_alarm_ctrl + REG_SET, 258f085f672SAnson Huang alarm_value << soc_data->high_alarm_shift); 25937713a1eSPhilipp Zabel } 26037713a1eSPhilipp Zabel 26117e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp) 262ca3de46bSShawn Guo { 263ca3de46bSShawn Guo struct imx_thermal_data *data = tz->devdata; 264f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 265ca3de46bSShawn Guo struct regmap *map = data->tempmon; 266ca3de46bSShawn Guo unsigned int n_meas; 26737713a1eSPhilipp Zabel bool wait; 268ca3de46bSShawn Guo u32 val; 269ca3de46bSShawn Guo 27037713a1eSPhilipp Zabel if (data->mode == THERMAL_DEVICE_ENABLED) { 27137713a1eSPhilipp Zabel /* Check if a measurement is currently in progress */ 272f085f672SAnson Huang regmap_read(map, soc_data->temp_data, &val); 273f085f672SAnson Huang wait = !(val & soc_data->temp_valid_mask); 27437713a1eSPhilipp Zabel } else { 275ca3de46bSShawn Guo /* 276ca3de46bSShawn Guo * Every time we measure the temperature, we will power on the 277ca3de46bSShawn Guo * temperature sensor, enable measurements, take a reading, 278ca3de46bSShawn Guo * disable measurements, power off the temperature sensor. 279ca3de46bSShawn Guo */ 280f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 281f085f672SAnson Huang soc_data->power_down_mask); 282f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 283f085f672SAnson Huang soc_data->measure_temp_mask); 284ca3de46bSShawn Guo 28537713a1eSPhilipp Zabel wait = true; 28637713a1eSPhilipp Zabel } 28737713a1eSPhilipp Zabel 288ca3de46bSShawn Guo /* 289ca3de46bSShawn Guo * According to the temp sensor designers, it may require up to ~17us 290ca3de46bSShawn Guo * to complete a measurement. 291ca3de46bSShawn Guo */ 29237713a1eSPhilipp Zabel if (wait) 293ca3de46bSShawn Guo usleep_range(20, 50); 294ca3de46bSShawn Guo 295f085f672SAnson Huang regmap_read(map, soc_data->temp_data, &val); 29637713a1eSPhilipp Zabel 29737713a1eSPhilipp Zabel if (data->mode != THERMAL_DEVICE_ENABLED) { 298f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 299f085f672SAnson Huang soc_data->measure_temp_mask); 300f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 301f085f672SAnson Huang soc_data->power_down_mask); 30237713a1eSPhilipp Zabel } 303ca3de46bSShawn Guo 304f085f672SAnson Huang if ((val & soc_data->temp_valid_mask) == 0) { 305ca3de46bSShawn Guo dev_dbg(&tz->device, "temp measurement never finished\n"); 306ca3de46bSShawn Guo return -EAGAIN; 307ca3de46bSShawn Guo } 308ca3de46bSShawn Guo 309f085f672SAnson Huang n_meas = (val & soc_data->temp_value_mask) 310f085f672SAnson Huang >> soc_data->temp_value_shift; 311ca3de46bSShawn Guo 312ae621557SLeonard Crestez /* See imx_init_calib() for formula derivation */ 313f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) 314f085f672SAnson Huang *temp = (n_meas - data->c1 + 25) * 1000; 315f085f672SAnson Huang else 316749e8be7SAnson Huang *temp = data->c2 - n_meas * data->c1; 317ca3de46bSShawn Guo 3183c94f17eSAnson Huang /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */ 3193c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6Q) { 3203c94f17eSAnson Huang if (data->alarm_temp == data->temp_passive && 3213c94f17eSAnson Huang *temp >= data->temp_passive) 32237713a1eSPhilipp Zabel imx_set_alarm_temp(data, data->temp_critical); 3233c94f17eSAnson Huang if (data->alarm_temp == data->temp_critical && 3243c94f17eSAnson Huang *temp < data->temp_passive) { 32537713a1eSPhilipp Zabel imx_set_alarm_temp(data, data->temp_passive); 32617e8351aSSascha Hauer dev_dbg(&tz->device, "thermal alarm off: T < %d\n", 32737713a1eSPhilipp Zabel data->alarm_temp / 1000); 32837713a1eSPhilipp Zabel } 3293c94f17eSAnson Huang } 33037713a1eSPhilipp Zabel 33137713a1eSPhilipp Zabel if (*temp != data->last_temp) { 33217e8351aSSascha Hauer dev_dbg(&tz->device, "millicelsius: %d\n", *temp); 33337713a1eSPhilipp Zabel data->last_temp = *temp; 33437713a1eSPhilipp Zabel } 33537713a1eSPhilipp Zabel 33637713a1eSPhilipp Zabel /* Reenable alarm IRQ if temperature below alarm temperature */ 33737713a1eSPhilipp Zabel if (!data->irq_enabled && *temp < data->alarm_temp) { 33837713a1eSPhilipp Zabel data->irq_enabled = true; 33937713a1eSPhilipp Zabel enable_irq(data->irq); 340ca3de46bSShawn Guo } 341ca3de46bSShawn Guo 342ca3de46bSShawn Guo return 0; 343ca3de46bSShawn Guo } 344ca3de46bSShawn Guo 345ca3de46bSShawn Guo static int imx_get_mode(struct thermal_zone_device *tz, 346ca3de46bSShawn Guo enum thermal_device_mode *mode) 347ca3de46bSShawn Guo { 348ca3de46bSShawn Guo struct imx_thermal_data *data = tz->devdata; 349ca3de46bSShawn Guo 350ca3de46bSShawn Guo *mode = data->mode; 351ca3de46bSShawn Guo 352ca3de46bSShawn Guo return 0; 353ca3de46bSShawn Guo } 354ca3de46bSShawn Guo 355ca3de46bSShawn Guo static int imx_set_mode(struct thermal_zone_device *tz, 356ca3de46bSShawn Guo enum thermal_device_mode mode) 357ca3de46bSShawn Guo { 358ca3de46bSShawn Guo struct imx_thermal_data *data = tz->devdata; 35937713a1eSPhilipp Zabel struct regmap *map = data->tempmon; 360f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 361ca3de46bSShawn Guo 362ca3de46bSShawn Guo if (mode == THERMAL_DEVICE_ENABLED) { 363ca3de46bSShawn Guo tz->polling_delay = IMX_POLLING_DELAY; 364ca3de46bSShawn Guo tz->passive_delay = IMX_PASSIVE_DELAY; 36537713a1eSPhilipp Zabel 366f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 367f085f672SAnson Huang soc_data->power_down_mask); 368f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 369f085f672SAnson Huang soc_data->measure_temp_mask); 37037713a1eSPhilipp Zabel 37137713a1eSPhilipp Zabel if (!data->irq_enabled) { 37237713a1eSPhilipp Zabel data->irq_enabled = true; 37337713a1eSPhilipp Zabel enable_irq(data->irq); 37437713a1eSPhilipp Zabel } 375ca3de46bSShawn Guo } else { 376f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 377f085f672SAnson Huang soc_data->measure_temp_mask); 378f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 379f085f672SAnson Huang soc_data->power_down_mask); 38037713a1eSPhilipp Zabel 381ca3de46bSShawn Guo tz->polling_delay = 0; 382ca3de46bSShawn Guo tz->passive_delay = 0; 38337713a1eSPhilipp Zabel 38437713a1eSPhilipp Zabel if (data->irq_enabled) { 38537713a1eSPhilipp Zabel disable_irq(data->irq); 38637713a1eSPhilipp Zabel data->irq_enabled = false; 38737713a1eSPhilipp Zabel } 388ca3de46bSShawn Guo } 389ca3de46bSShawn Guo 390ca3de46bSShawn Guo data->mode = mode; 3910e70f466SSrinivas Pandruvada thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); 392ca3de46bSShawn Guo 393ca3de46bSShawn Guo return 0; 394ca3de46bSShawn Guo } 395ca3de46bSShawn Guo 396ca3de46bSShawn Guo static int imx_get_trip_type(struct thermal_zone_device *tz, int trip, 397ca3de46bSShawn Guo enum thermal_trip_type *type) 398ca3de46bSShawn Guo { 399ca3de46bSShawn Guo *type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE : 400ca3de46bSShawn Guo THERMAL_TRIP_CRITICAL; 401ca3de46bSShawn Guo return 0; 402ca3de46bSShawn Guo } 403ca3de46bSShawn Guo 40417e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp) 405ca3de46bSShawn Guo { 406017e5142SPhilipp Zabel struct imx_thermal_data *data = tz->devdata; 407017e5142SPhilipp Zabel 408017e5142SPhilipp Zabel *temp = data->temp_critical; 409ca3de46bSShawn Guo return 0; 410ca3de46bSShawn Guo } 411ca3de46bSShawn Guo 412ca3de46bSShawn Guo static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip, 41317e8351aSSascha Hauer int *temp) 414ca3de46bSShawn Guo { 415017e5142SPhilipp Zabel struct imx_thermal_data *data = tz->devdata; 416017e5142SPhilipp Zabel 417017e5142SPhilipp Zabel *temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive : 418017e5142SPhilipp Zabel data->temp_critical; 419017e5142SPhilipp Zabel return 0; 420017e5142SPhilipp Zabel } 421017e5142SPhilipp Zabel 422017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, 42317e8351aSSascha Hauer int temp) 424017e5142SPhilipp Zabel { 425017e5142SPhilipp Zabel struct imx_thermal_data *data = tz->devdata; 426017e5142SPhilipp Zabel 427a2291badSTim Harvey /* do not allow changing critical threshold */ 428017e5142SPhilipp Zabel if (trip == IMX_TRIP_CRITICAL) 429017e5142SPhilipp Zabel return -EPERM; 430017e5142SPhilipp Zabel 431a2291badSTim Harvey /* do not allow passive to be set higher than critical */ 432a2291badSTim Harvey if (temp < 0 || temp > data->temp_critical) 433017e5142SPhilipp Zabel return -EINVAL; 434017e5142SPhilipp Zabel 435017e5142SPhilipp Zabel data->temp_passive = temp; 436017e5142SPhilipp Zabel 43737713a1eSPhilipp Zabel imx_set_alarm_temp(data, temp); 43837713a1eSPhilipp Zabel 439ca3de46bSShawn Guo return 0; 440ca3de46bSShawn Guo } 441ca3de46bSShawn Guo 442ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz, 443ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 444ca3de46bSShawn Guo { 445ca3de46bSShawn Guo int ret; 446ca3de46bSShawn Guo 447ca3de46bSShawn Guo ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, 448ca3de46bSShawn Guo THERMAL_NO_LIMIT, 4496cd9e9f6SKapileshwar Singh THERMAL_NO_LIMIT, 4506cd9e9f6SKapileshwar Singh THERMAL_WEIGHT_DEFAULT); 451ca3de46bSShawn Guo if (ret) { 452ca3de46bSShawn Guo dev_err(&tz->device, 453ca3de46bSShawn Guo "binding zone %s with cdev %s failed:%d\n", 454ca3de46bSShawn Guo tz->type, cdev->type, ret); 455ca3de46bSShawn Guo return ret; 456ca3de46bSShawn Guo } 457ca3de46bSShawn Guo 458ca3de46bSShawn Guo return 0; 459ca3de46bSShawn Guo } 460ca3de46bSShawn Guo 461ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz, 462ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 463ca3de46bSShawn Guo { 464ca3de46bSShawn Guo int ret; 465ca3de46bSShawn Guo 466ca3de46bSShawn Guo ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); 467ca3de46bSShawn Guo if (ret) { 468ca3de46bSShawn Guo dev_err(&tz->device, 469ca3de46bSShawn Guo "unbinding zone %s with cdev %s failed:%d\n", 470ca3de46bSShawn Guo tz->type, cdev->type, ret); 471ca3de46bSShawn Guo return ret; 472ca3de46bSShawn Guo } 473ca3de46bSShawn Guo 474ca3de46bSShawn Guo return 0; 475ca3de46bSShawn Guo } 476ca3de46bSShawn Guo 477cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = { 478ca3de46bSShawn Guo .bind = imx_bind, 479ca3de46bSShawn Guo .unbind = imx_unbind, 480ca3de46bSShawn Guo .get_temp = imx_get_temp, 481ca3de46bSShawn Guo .get_mode = imx_get_mode, 482ca3de46bSShawn Guo .set_mode = imx_set_mode, 483ca3de46bSShawn Guo .get_trip_type = imx_get_trip_type, 484ca3de46bSShawn Guo .get_trip_temp = imx_get_trip_temp, 485ca3de46bSShawn Guo .get_crit_temp = imx_get_crit_temp, 486017e5142SPhilipp Zabel .set_trip_temp = imx_set_trip_temp, 487ca3de46bSShawn Guo }; 488ca3de46bSShawn Guo 489e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1) 490ca3de46bSShawn Guo { 491ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 4924e5f61caSUwe Kleine-König int n1; 493749e8be7SAnson Huang u64 temp64; 494ca3de46bSShawn Guo 495e4bb2240SUwe Kleine-König if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) { 496ca3de46bSShawn Guo dev_err(&pdev->dev, "invalid sensor calibration data\n"); 497ca3de46bSShawn Guo return -EINVAL; 498ca3de46bSShawn Guo } 499ca3de46bSShawn Guo 500ca3de46bSShawn Guo /* 501f085f672SAnson Huang * On i.MX7D, we only use the calibration data at 25C to get the temp, 502f085f672SAnson Huang * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C. 503f085f672SAnson Huang */ 504f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) { 505f085f672SAnson Huang data->c1 = (ocotp_ana1 >> 9) & 0x1ff; 506f085f672SAnson Huang return 0; 507f085f672SAnson Huang } 508f085f672SAnson Huang 509f085f672SAnson Huang /* 510c5bbdb4bSUwe Kleine-König * The sensor is calibrated at 25 °C (aka T1) and the value measured 511c5bbdb4bSUwe Kleine-König * (aka N1) at this temperature is provided in bits [31:20] in the 512c5bbdb4bSUwe Kleine-König * i.MX's OCOTP value ANA1. 513c5bbdb4bSUwe Kleine-König * To find the actual temperature T, the following formula has to be used 514c5bbdb4bSUwe Kleine-König * when reading value n from the sensor: 515c5bbdb4bSUwe Kleine-König * 5164e5f61caSUwe Kleine-König * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C 5174e5f61caSUwe Kleine-König * = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C 5184e5f61caSUwe Kleine-König * = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C 519c5bbdb4bSUwe Kleine-König * = c2 - c1 * N 520c5bbdb4bSUwe Kleine-König * 521c5bbdb4bSUwe Kleine-König * with 522c5bbdb4bSUwe Kleine-König * 5234e5f61caSUwe Kleine-König * T1' = 28.580661 °C 5244e5f61caSUwe Kleine-König * c1 = 1 / (0.0015423 * N1 - 0.4297157) °C 5254e5f61caSUwe Kleine-König * c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C 5264e5f61caSUwe Kleine-König * = T1' + N1 * c1 527ca3de46bSShawn Guo */ 528e4bb2240SUwe Kleine-König n1 = ocotp_ana1 >> 20; 529ca3de46bSShawn Guo 5304e5f61caSUwe Kleine-König temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */ 531c5bbdb4bSUwe Kleine-König temp64 *= 1000; /* to get result in °mC */ 5324e5f61caSUwe Kleine-König do_div(temp64, 15423 * n1 - 4148468); 533749e8be7SAnson Huang data->c1 = temp64; 5344e5f61caSUwe Kleine-König data->c2 = n1 * data->c1 + 28581; 535ca3de46bSShawn Guo 536ae621557SLeonard Crestez return 0; 537a2291badSTim Harvey } 538a2291badSTim Harvey 539e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0) 540ae621557SLeonard Crestez { 541ae621557SLeonard Crestez struct imx_thermal_data *data = platform_get_drvdata(pdev); 542ae621557SLeonard Crestez 543a2291badSTim Harvey /* The maximum die temp is specified by the Temperature Grade */ 544e4bb2240SUwe Kleine-König switch ((ocotp_mem0 >> 6) & 0x3) { 545339d7492SUwe Kleine-König case 0: /* Commercial (0 to 95 °C) */ 546a2291badSTim Harvey data->temp_grade = "Commercial"; 547a2291badSTim Harvey data->temp_max = 95000; 548a2291badSTim Harvey break; 549339d7492SUwe Kleine-König case 1: /* Extended Commercial (-20 °C to 105 °C) */ 550a2291badSTim Harvey data->temp_grade = "Extended Commercial"; 551a2291badSTim Harvey data->temp_max = 105000; 552a2291badSTim Harvey break; 553339d7492SUwe Kleine-König case 2: /* Industrial (-40 °C to 105 °C) */ 554a2291badSTim Harvey data->temp_grade = "Industrial"; 555a2291badSTim Harvey data->temp_max = 105000; 556a2291badSTim Harvey break; 557339d7492SUwe Kleine-König case 3: /* Automotive (-40 °C to 125 °C) */ 558a2291badSTim Harvey data->temp_grade = "Automotive"; 559a2291badSTim Harvey data->temp_max = 125000; 560a2291badSTim Harvey break; 561a2291badSTim Harvey } 562017e5142SPhilipp Zabel 563017e5142SPhilipp Zabel /* 564339d7492SUwe Kleine-König * Set the critical trip point at 5 °C under max 565339d7492SUwe Kleine-König * Set the passive trip point at 10 °C under max (changeable via sysfs) 566017e5142SPhilipp Zabel */ 567a2291badSTim Harvey data->temp_critical = data->temp_max - (1000 * 5); 568a2291badSTim Harvey data->temp_passive = data->temp_max - (1000 * 10); 569ae621557SLeonard Crestez } 570ae621557SLeonard Crestez 571ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev) 572ae621557SLeonard Crestez { 573ae621557SLeonard Crestez struct regmap *map; 574ae621557SLeonard Crestez int ret; 575ae621557SLeonard Crestez u32 val; 576ae621557SLeonard Crestez 577ae621557SLeonard Crestez map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, 578ae621557SLeonard Crestez "fsl,tempmon-data"); 579ae621557SLeonard Crestez if (IS_ERR(map)) { 580ae621557SLeonard Crestez ret = PTR_ERR(map); 581ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); 582ae621557SLeonard Crestez return ret; 583ae621557SLeonard Crestez } 584ae621557SLeonard Crestez 585ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_ANA1, &val); 586ae621557SLeonard Crestez if (ret) { 587ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 588ae621557SLeonard Crestez return ret; 589ae621557SLeonard Crestez } 590ae621557SLeonard Crestez ret = imx_init_calib(pdev, val); 591ae621557SLeonard Crestez if (ret) 592ae621557SLeonard Crestez return ret; 593ae621557SLeonard Crestez 594ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_MEM0, &val); 595ae621557SLeonard Crestez if (ret) { 596ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 597ae621557SLeonard Crestez return ret; 598ae621557SLeonard Crestez } 599ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 600ae621557SLeonard Crestez 601ae621557SLeonard Crestez return 0; 602ae621557SLeonard Crestez } 603ae621557SLeonard Crestez 604ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev) 605ae621557SLeonard Crestez { 606ae621557SLeonard Crestez int ret; 607ae621557SLeonard Crestez u32 val; 608ae621557SLeonard Crestez 609ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val); 610ae621557SLeonard Crestez if (ret) 611ae621557SLeonard Crestez return ret; 612ae621557SLeonard Crestez imx_init_calib(pdev, val); 613ae621557SLeonard Crestez 614ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val); 615ae621557SLeonard Crestez if (ret) 616ae621557SLeonard Crestez return ret; 617ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 618017e5142SPhilipp Zabel 619ca3de46bSShawn Guo return 0; 620ca3de46bSShawn Guo } 621ca3de46bSShawn Guo 62237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev) 62337713a1eSPhilipp Zabel { 62437713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 62537713a1eSPhilipp Zabel 62637713a1eSPhilipp Zabel disable_irq_nosync(irq); 62737713a1eSPhilipp Zabel data->irq_enabled = false; 62837713a1eSPhilipp Zabel 62937713a1eSPhilipp Zabel return IRQ_WAKE_THREAD; 63037713a1eSPhilipp Zabel } 63137713a1eSPhilipp Zabel 63237713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) 63337713a1eSPhilipp Zabel { 63437713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 63537713a1eSPhilipp Zabel 63617e8351aSSascha Hauer dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n", 63737713a1eSPhilipp Zabel data->alarm_temp / 1000); 63837713a1eSPhilipp Zabel 6390e70f466SSrinivas Pandruvada thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED); 64037713a1eSPhilipp Zabel 64137713a1eSPhilipp Zabel return IRQ_HANDLED; 64237713a1eSPhilipp Zabel } 64337713a1eSPhilipp Zabel 6443c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = { 6453c94f17eSAnson Huang { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, }, 6463c94f17eSAnson Huang { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, }, 647f085f672SAnson Huang { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, }, 6483c94f17eSAnson Huang { /* end */ } 6493c94f17eSAnson Huang }; 6503c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match); 6513c94f17eSAnson Huang 652ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev) 653ca3de46bSShawn Guo { 654ca3de46bSShawn Guo struct imx_thermal_data *data; 655ca3de46bSShawn Guo struct regmap *map; 65637713a1eSPhilipp Zabel int measure_freq; 657ca3de46bSShawn Guo int ret; 658ca3de46bSShawn Guo 659ca3de46bSShawn Guo data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 660ca3de46bSShawn Guo if (!data) 661ca3de46bSShawn Guo return -ENOMEM; 662ca3de46bSShawn Guo 663ca3de46bSShawn Guo map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); 664ca3de46bSShawn Guo if (IS_ERR(map)) { 665ca3de46bSShawn Guo ret = PTR_ERR(map); 666ca3de46bSShawn Guo dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); 667ca3de46bSShawn Guo return ret; 668ca3de46bSShawn Guo } 669ca3de46bSShawn Guo data->tempmon = map; 670ca3de46bSShawn Guo 671829bc78aSCorentin LABBE data->socdata = of_device_get_match_data(&pdev->dev); 6728b051ec3SShailendra Verma if (!data->socdata) { 6738b051ec3SShailendra Verma dev_err(&pdev->dev, "no device match found\n"); 6748b051ec3SShailendra Verma return -ENODEV; 6758b051ec3SShailendra Verma } 6763c94f17eSAnson Huang 6773c94f17eSAnson Huang /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */ 6783c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) { 679f085f672SAnson Huang regmap_write(map, IMX6_MISC1 + REG_CLR, 680f085f672SAnson Huang IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW 681f085f672SAnson Huang | IMX6_MISC1_IRQ_TEMPPANIC); 6823c94f17eSAnson Huang /* 6833c94f17eSAnson Huang * reset value of LOW ALARM is incorrect, set it to lowest 6843c94f17eSAnson Huang * value to avoid false trigger of low alarm. 6853c94f17eSAnson Huang */ 686f085f672SAnson Huang regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET, 687f085f672SAnson Huang data->socdata->low_alarm_mask); 6883c94f17eSAnson Huang } 6893c94f17eSAnson Huang 69037713a1eSPhilipp Zabel data->irq = platform_get_irq(pdev, 0); 69137713a1eSPhilipp Zabel if (data->irq < 0) 69237713a1eSPhilipp Zabel return data->irq; 69337713a1eSPhilipp Zabel 694ca3de46bSShawn Guo platform_set_drvdata(pdev, data); 695ca3de46bSShawn Guo 696ae621557SLeonard Crestez if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) { 697ae621557SLeonard Crestez ret = imx_init_from_nvmem_cells(pdev); 698ae621557SLeonard Crestez if (ret == -EPROBE_DEFER) 699ca3de46bSShawn Guo return ret; 700ae621557SLeonard Crestez if (ret) { 701ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to init from nvmem: %d\n", 702ae621557SLeonard Crestez ret); 703ae621557SLeonard Crestez return ret; 704ae621557SLeonard Crestez } 705ae621557SLeonard Crestez } else { 706ae621557SLeonard Crestez ret = imx_init_from_tempmon_data(pdev); 707ae621557SLeonard Crestez if (ret) { 708ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to init from from fsl,tempmon-data\n"); 709ae621557SLeonard Crestez return ret; 710ae621557SLeonard Crestez } 711ca3de46bSShawn Guo } 712ca3de46bSShawn Guo 713ca3de46bSShawn Guo /* Make sure sensor is in known good state for measurements */ 714f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 715f085f672SAnson Huang data->socdata->power_down_mask); 716f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 717f085f672SAnson Huang data->socdata->measure_temp_mask); 718f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, 719f085f672SAnson Huang data->socdata->measure_freq_mask); 720f085f672SAnson Huang if (data->socdata->version != TEMPMON_IMX7D) 721f085f672SAnson Huang regmap_write(map, IMX6_MISC0 + REG_SET, 722f085f672SAnson Huang IMX6_MISC0_REFTOP_SELBIASOFF); 723f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 724f085f672SAnson Huang data->socdata->power_down_mask); 725ca3de46bSShawn Guo 7264d753aa7SViresh Kumar data->policy = cpufreq_cpu_get(0); 7274d753aa7SViresh Kumar if (!data->policy) { 7284d753aa7SViresh Kumar pr_debug("%s: CPUFreq policy not found\n", __func__); 7294d753aa7SViresh Kumar return -EPROBE_DEFER; 7304d753aa7SViresh Kumar } 7314d753aa7SViresh Kumar 7324d753aa7SViresh Kumar data->cdev = cpufreq_cooling_register(data->policy); 733ca3de46bSShawn Guo if (IS_ERR(data->cdev)) { 734ca3de46bSShawn Guo ret = PTR_ERR(data->cdev); 735ca3de46bSShawn Guo dev_err(&pdev->dev, 7364d753aa7SViresh Kumar "failed to register cpufreq cooling device: %d\n", ret); 7374d753aa7SViresh Kumar cpufreq_cpu_put(data->policy); 738ca3de46bSShawn Guo return ret; 739ca3de46bSShawn Guo } 740ca3de46bSShawn Guo 74190a21ff5SHeiner Kallweit data->thermal_clk = devm_clk_get(&pdev->dev, NULL); 74290a21ff5SHeiner Kallweit if (IS_ERR(data->thermal_clk)) { 74390a21ff5SHeiner Kallweit ret = PTR_ERR(data->thermal_clk); 74490a21ff5SHeiner Kallweit if (ret != -EPROBE_DEFER) 74590a21ff5SHeiner Kallweit dev_err(&pdev->dev, 74690a21ff5SHeiner Kallweit "failed to get thermal clk: %d\n", ret); 74790a21ff5SHeiner Kallweit cpufreq_cooling_unregister(data->cdev); 7484d753aa7SViresh Kumar cpufreq_cpu_put(data->policy); 74990a21ff5SHeiner Kallweit return ret; 75090a21ff5SHeiner Kallweit } 75190a21ff5SHeiner Kallweit 75290a21ff5SHeiner Kallweit /* 75390a21ff5SHeiner Kallweit * Thermal sensor needs clk on to get correct value, normally 75490a21ff5SHeiner Kallweit * we should enable its clk before taking measurement and disable 75590a21ff5SHeiner Kallweit * clk after measurement is done, but if alarm function is enabled, 75690a21ff5SHeiner Kallweit * hardware will auto measure the temperature periodically, so we 75790a21ff5SHeiner Kallweit * need to keep the clk always on for alarm function. 75890a21ff5SHeiner Kallweit */ 75990a21ff5SHeiner Kallweit ret = clk_prepare_enable(data->thermal_clk); 76090a21ff5SHeiner Kallweit if (ret) { 76190a21ff5SHeiner Kallweit dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret); 76290a21ff5SHeiner Kallweit cpufreq_cooling_unregister(data->cdev); 7634d753aa7SViresh Kumar cpufreq_cpu_put(data->policy); 76490a21ff5SHeiner Kallweit return ret; 76590a21ff5SHeiner Kallweit } 76690a21ff5SHeiner Kallweit 767ca3de46bSShawn Guo data->tz = thermal_zone_device_register("imx_thermal_zone", 768017e5142SPhilipp Zabel IMX_TRIP_NUM, 769017e5142SPhilipp Zabel BIT(IMX_TRIP_PASSIVE), data, 770ca3de46bSShawn Guo &imx_tz_ops, NULL, 771ca3de46bSShawn Guo IMX_PASSIVE_DELAY, 772ca3de46bSShawn Guo IMX_POLLING_DELAY); 773ca3de46bSShawn Guo if (IS_ERR(data->tz)) { 774ca3de46bSShawn Guo ret = PTR_ERR(data->tz); 775ca3de46bSShawn Guo dev_err(&pdev->dev, 776ca3de46bSShawn Guo "failed to register thermal zone device %d\n", ret); 77790a21ff5SHeiner Kallweit clk_disable_unprepare(data->thermal_clk); 778ca3de46bSShawn Guo cpufreq_cooling_unregister(data->cdev); 7794d753aa7SViresh Kumar cpufreq_cpu_put(data->policy); 780ca3de46bSShawn Guo return ret; 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; 805cf1ba1d7SMikhail Lappo data->mode = THERMAL_DEVICE_ENABLED; 806cf1ba1d7SMikhail Lappo 80784866ee5SBai Ping ret = devm_request_threaded_irq(&pdev->dev, data->irq, 80884866ee5SBai Ping imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, 80984866ee5SBai Ping 0, "imx_thermal", data); 81084866ee5SBai Ping if (ret < 0) { 81184866ee5SBai Ping dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); 81284866ee5SBai Ping clk_disable_unprepare(data->thermal_clk); 81384866ee5SBai Ping thermal_zone_device_unregister(data->tz); 81484866ee5SBai Ping cpufreq_cooling_unregister(data->cdev); 8154d753aa7SViresh Kumar cpufreq_cpu_put(data->policy); 81684866ee5SBai Ping return ret; 81784866ee5SBai Ping } 81884866ee5SBai Ping 819ca3de46bSShawn Guo return 0; 820ca3de46bSShawn Guo } 821ca3de46bSShawn Guo 822ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev) 823ca3de46bSShawn Guo { 824ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 82537713a1eSPhilipp Zabel struct regmap *map = data->tempmon; 82637713a1eSPhilipp Zabel 82737713a1eSPhilipp Zabel /* Disable measurements */ 828f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 829f085f672SAnson Huang data->socdata->power_down_mask); 830329fe7b1SAnson Huang if (!IS_ERR(data->thermal_clk)) 831329fe7b1SAnson Huang clk_disable_unprepare(data->thermal_clk); 832ca3de46bSShawn Guo 833ca3de46bSShawn Guo thermal_zone_device_unregister(data->tz); 834ca3de46bSShawn Guo cpufreq_cooling_unregister(data->cdev); 8354d753aa7SViresh Kumar cpufreq_cpu_put(data->policy); 836ca3de46bSShawn Guo 837ca3de46bSShawn Guo return 0; 838ca3de46bSShawn Guo } 839ca3de46bSShawn Guo 840ca3de46bSShawn Guo #ifdef CONFIG_PM_SLEEP 841ca3de46bSShawn Guo static int imx_thermal_suspend(struct device *dev) 842ca3de46bSShawn Guo { 843ca3de46bSShawn Guo struct imx_thermal_data *data = dev_get_drvdata(dev); 844ca3de46bSShawn Guo struct regmap *map = data->tempmon; 845ca3de46bSShawn Guo 846ca3de46bSShawn Guo /* 847b46cce59SAnson Huang * Need to disable thermal sensor, otherwise, when thermal core 848b46cce59SAnson Huang * try to get temperature before thermal sensor resume, a wrong 849b46cce59SAnson Huang * temperature will be read as the thermal sensor is powered 850b46cce59SAnson Huang * down. 851ca3de46bSShawn Guo */ 852f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 853f085f672SAnson Huang data->socdata->measure_temp_mask); 854f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 855f085f672SAnson Huang data->socdata->power_down_mask); 856b46cce59SAnson Huang data->mode = THERMAL_DEVICE_DISABLED; 857d26eef8bSAnson Huang clk_disable_unprepare(data->thermal_clk); 858ca3de46bSShawn Guo 859ca3de46bSShawn Guo return 0; 860ca3de46bSShawn Guo } 861ca3de46bSShawn Guo 862ca3de46bSShawn Guo static int imx_thermal_resume(struct device *dev) 863ca3de46bSShawn Guo { 864b46cce59SAnson Huang struct imx_thermal_data *data = dev_get_drvdata(dev); 865b46cce59SAnson Huang struct regmap *map = data->tempmon; 866e3bdc8d7SArvind Yadav int ret; 867b46cce59SAnson Huang 868e3bdc8d7SArvind Yadav ret = clk_prepare_enable(data->thermal_clk); 869e3bdc8d7SArvind Yadav if (ret) 870e3bdc8d7SArvind Yadav return ret; 871b46cce59SAnson Huang /* Enabled thermal sensor after resume */ 872f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 873f085f672SAnson Huang data->socdata->power_down_mask); 874f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 875f085f672SAnson Huang data->socdata->measure_temp_mask); 876b46cce59SAnson Huang data->mode = THERMAL_DEVICE_ENABLED; 877b46cce59SAnson Huang 878ca3de46bSShawn Guo return 0; 879ca3de46bSShawn Guo } 880ca3de46bSShawn Guo #endif 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