145f8b0ddSFabio Estevam // SPDX-License-Identifier: GPL-2.0 245f8b0ddSFabio Estevam // 345f8b0ddSFabio Estevam // Copyright 2013 Freescale Semiconductor, Inc. 4ca3de46bSShawn Guo 5329fe7b1SAnson Huang #include <linux/clk.h> 64d753aa7SViresh Kumar #include <linux/cpufreq.h> 7ca3de46bSShawn Guo #include <linux/cpu_cooling.h> 8ca3de46bSShawn Guo #include <linux/delay.h> 937713a1eSPhilipp Zabel #include <linux/interrupt.h> 10ca3de46bSShawn Guo #include <linux/io.h> 11ca3de46bSShawn Guo #include <linux/mfd/syscon.h> 12ca3de46bSShawn Guo #include <linux/module.h> 13ca3de46bSShawn Guo #include <linux/of.h> 143c94f17eSAnson Huang #include <linux/of_device.h> 15ca3de46bSShawn Guo #include <linux/regmap.h> 16ca3de46bSShawn Guo #include <linux/thermal.h> 17ae621557SLeonard Crestez #include <linux/nvmem-consumer.h> 18ca3de46bSShawn Guo 19ca3de46bSShawn Guo #define REG_SET 0x4 20ca3de46bSShawn Guo #define REG_CLR 0x8 21ca3de46bSShawn Guo #define REG_TOG 0xc 22ca3de46bSShawn Guo 23f085f672SAnson Huang /* i.MX6 specific */ 24f085f672SAnson Huang #define IMX6_MISC0 0x0150 25f085f672SAnson Huang #define IMX6_MISC0_REFTOP_SELBIASOFF (1 << 3) 26f085f672SAnson Huang #define IMX6_MISC1 0x0160 27f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPHIGH (1 << 29) 283c94f17eSAnson Huang /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */ 29f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPLOW (1 << 28) 30f085f672SAnson Huang #define IMX6_MISC1_IRQ_TEMPPANIC (1 << 27) 31ca3de46bSShawn Guo 32f085f672SAnson Huang #define IMX6_TEMPSENSE0 0x0180 33f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT 20 34f085f672SAnson Huang #define IMX6_TEMPSENSE0_ALARM_VALUE_MASK (0xfff << 20) 35f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT 8 36f085f672SAnson Huang #define IMX6_TEMPSENSE0_TEMP_CNT_MASK (0xfff << 8) 37f085f672SAnson Huang #define IMX6_TEMPSENSE0_FINISHED (1 << 2) 38f085f672SAnson Huang #define IMX6_TEMPSENSE0_MEASURE_TEMP (1 << 1) 39f085f672SAnson Huang #define IMX6_TEMPSENSE0_POWER_DOWN (1 << 0) 40ca3de46bSShawn Guo 41f085f672SAnson Huang #define IMX6_TEMPSENSE1 0x0190 42f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ 0xffff 43f085f672SAnson Huang #define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT 0 44ca3de46bSShawn Guo 45a2291badSTim Harvey #define OCOTP_MEM0 0x0480 46ca3de46bSShawn Guo #define OCOTP_ANA1 0x04e0 47ca3de46bSShawn Guo 48f085f672SAnson Huang /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */ 49f085f672SAnson Huang #define IMX6_TEMPSENSE2 0x0290 50f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT 0 51f085f672SAnson Huang #define IMX6_TEMPSENSE2_LOW_VALUE_MASK 0xfff 52f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT 16 53f085f672SAnson Huang #define IMX6_TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000 54f085f672SAnson Huang 55f085f672SAnson Huang /* i.MX7 specific */ 56f085f672SAnson Huang #define IMX7_ANADIG_DIGPROG 0x800 57f085f672SAnson Huang #define IMX7_TEMPSENSE0 0x300 58f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT 18 59f085f672SAnson Huang #define IMX7_TEMPSENSE0_PANIC_ALARM_MASK (0x1ff << 18) 60f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT 9 61f085f672SAnson Huang #define IMX7_TEMPSENSE0_HIGH_ALARM_MASK (0x1ff << 9) 62f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT 0 63f085f672SAnson Huang #define IMX7_TEMPSENSE0_LOW_ALARM_MASK 0x1ff 64f085f672SAnson Huang 65f085f672SAnson Huang #define IMX7_TEMPSENSE1 0x310 66f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT 16 67f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK (0xffff << 16) 68f085f672SAnson Huang #define IMX7_TEMPSENSE1_FINISHED (1 << 11) 69f085f672SAnson Huang #define IMX7_TEMPSENSE1_MEASURE_TEMP (1 << 10) 70f085f672SAnson Huang #define IMX7_TEMPSENSE1_POWER_DOWN (1 << 9) 71f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT 0 72f085f672SAnson Huang #define IMX7_TEMPSENSE1_TEMP_VALUE_MASK 0x1ff 73f085f672SAnson Huang 74ca3de46bSShawn Guo /* The driver supports 1 passive trip point and 1 critical trip point */ 75ca3de46bSShawn Guo enum imx_thermal_trip { 76ca3de46bSShawn Guo IMX_TRIP_PASSIVE, 77ca3de46bSShawn Guo IMX_TRIP_CRITICAL, 78ca3de46bSShawn Guo IMX_TRIP_NUM, 79ca3de46bSShawn Guo }; 80ca3de46bSShawn Guo 81ca3de46bSShawn Guo #define IMX_POLLING_DELAY 2000 /* millisecond */ 82ca3de46bSShawn Guo #define IMX_PASSIVE_DELAY 1000 83ca3de46bSShawn Guo 843c94f17eSAnson Huang #define TEMPMON_IMX6Q 1 853c94f17eSAnson Huang #define TEMPMON_IMX6SX 2 86f085f672SAnson Huang #define TEMPMON_IMX7D 3 873c94f17eSAnson Huang 883c94f17eSAnson Huang struct thermal_soc_data { 893c94f17eSAnson Huang u32 version; 90f085f672SAnson Huang 91f085f672SAnson Huang u32 sensor_ctrl; 92f085f672SAnson Huang u32 power_down_mask; 93f085f672SAnson Huang u32 measure_temp_mask; 94f085f672SAnson Huang 95f085f672SAnson Huang u32 measure_freq_ctrl; 96f085f672SAnson Huang u32 measure_freq_mask; 97f085f672SAnson Huang u32 measure_freq_shift; 98f085f672SAnson Huang 99f085f672SAnson Huang u32 temp_data; 100f085f672SAnson Huang u32 temp_value_mask; 101f085f672SAnson Huang u32 temp_value_shift; 102f085f672SAnson Huang u32 temp_valid_mask; 103f085f672SAnson Huang 104f085f672SAnson Huang u32 panic_alarm_ctrl; 105f085f672SAnson Huang u32 panic_alarm_mask; 106f085f672SAnson Huang u32 panic_alarm_shift; 107f085f672SAnson Huang 108f085f672SAnson Huang u32 high_alarm_ctrl; 109f085f672SAnson Huang u32 high_alarm_mask; 110f085f672SAnson Huang u32 high_alarm_shift; 111f085f672SAnson Huang 112f085f672SAnson Huang u32 low_alarm_ctrl; 113f085f672SAnson Huang u32 low_alarm_mask; 114f085f672SAnson Huang u32 low_alarm_shift; 1153c94f17eSAnson Huang }; 1163c94f17eSAnson Huang 1173c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6q_data = { 1183c94f17eSAnson Huang .version = TEMPMON_IMX6Q, 119f085f672SAnson Huang 120f085f672SAnson Huang .sensor_ctrl = IMX6_TEMPSENSE0, 121f085f672SAnson Huang .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, 122f085f672SAnson Huang .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, 123f085f672SAnson Huang 124f085f672SAnson Huang .measure_freq_ctrl = IMX6_TEMPSENSE1, 125f085f672SAnson Huang .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, 126f085f672SAnson Huang .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, 127f085f672SAnson Huang 128f085f672SAnson Huang .temp_data = IMX6_TEMPSENSE0, 129f085f672SAnson Huang .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, 130f085f672SAnson Huang .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, 131f085f672SAnson Huang .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, 132f085f672SAnson Huang 133f085f672SAnson Huang .high_alarm_ctrl = IMX6_TEMPSENSE0, 134f085f672SAnson Huang .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, 135f085f672SAnson Huang .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, 1363c94f17eSAnson Huang }; 1373c94f17eSAnson Huang 1383c94f17eSAnson Huang static struct thermal_soc_data thermal_imx6sx_data = { 1393c94f17eSAnson Huang .version = TEMPMON_IMX6SX, 140f085f672SAnson Huang 141f085f672SAnson Huang .sensor_ctrl = IMX6_TEMPSENSE0, 142f085f672SAnson Huang .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, 143f085f672SAnson Huang .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, 144f085f672SAnson Huang 145f085f672SAnson Huang .measure_freq_ctrl = IMX6_TEMPSENSE1, 146f085f672SAnson Huang .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, 147f085f672SAnson Huang .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, 148f085f672SAnson Huang 149f085f672SAnson Huang .temp_data = IMX6_TEMPSENSE0, 150f085f672SAnson Huang .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, 151f085f672SAnson Huang .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, 152f085f672SAnson Huang .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, 153f085f672SAnson Huang 154f085f672SAnson Huang .high_alarm_ctrl = IMX6_TEMPSENSE0, 155f085f672SAnson Huang .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, 156f085f672SAnson Huang .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, 157f085f672SAnson Huang 158f085f672SAnson Huang .panic_alarm_ctrl = IMX6_TEMPSENSE2, 159f085f672SAnson Huang .panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK, 160f085f672SAnson Huang .panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT, 161f085f672SAnson Huang 162f085f672SAnson Huang .low_alarm_ctrl = IMX6_TEMPSENSE2, 163f085f672SAnson Huang .low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK, 164f085f672SAnson Huang .low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT, 165f085f672SAnson Huang }; 166f085f672SAnson Huang 167f085f672SAnson Huang static struct thermal_soc_data thermal_imx7d_data = { 168f085f672SAnson Huang .version = TEMPMON_IMX7D, 169f085f672SAnson Huang 170f085f672SAnson Huang .sensor_ctrl = IMX7_TEMPSENSE1, 171f085f672SAnson Huang .power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN, 172f085f672SAnson Huang .measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP, 173f085f672SAnson Huang 174f085f672SAnson Huang .measure_freq_ctrl = IMX7_TEMPSENSE1, 175f085f672SAnson Huang .measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT, 176f085f672SAnson Huang .measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK, 177f085f672SAnson Huang 178f085f672SAnson Huang .temp_data = IMX7_TEMPSENSE1, 179f085f672SAnson Huang .temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK, 180f085f672SAnson Huang .temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT, 181f085f672SAnson Huang .temp_valid_mask = IMX7_TEMPSENSE1_FINISHED, 182f085f672SAnson Huang 183f085f672SAnson Huang .panic_alarm_ctrl = IMX7_TEMPSENSE1, 184f085f672SAnson Huang .panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK, 185f085f672SAnson Huang .panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT, 186f085f672SAnson Huang 187f085f672SAnson Huang .high_alarm_ctrl = IMX7_TEMPSENSE0, 188f085f672SAnson Huang .high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK, 189f085f672SAnson Huang .high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT, 190f085f672SAnson Huang 191f085f672SAnson Huang .low_alarm_ctrl = IMX7_TEMPSENSE0, 192f085f672SAnson Huang .low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK, 193f085f672SAnson Huang .low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT, 1943c94f17eSAnson Huang }; 1953c94f17eSAnson Huang 196ca3de46bSShawn Guo struct imx_thermal_data { 1974d753aa7SViresh Kumar struct cpufreq_policy *policy; 198ca3de46bSShawn Guo struct thermal_zone_device *tz; 199ca3de46bSShawn Guo struct thermal_cooling_device *cdev; 200ca3de46bSShawn Guo struct regmap *tempmon; 201ae621557SLeonard Crestez u32 c1, c2; /* See formula in imx_init_calib() */ 20217e8351aSSascha Hauer int temp_passive; 20317e8351aSSascha Hauer int temp_critical; 204a2291badSTim Harvey int temp_max; 20517e8351aSSascha Hauer int alarm_temp; 20617e8351aSSascha Hauer int last_temp; 20737713a1eSPhilipp Zabel bool irq_enabled; 20837713a1eSPhilipp Zabel int irq; 209329fe7b1SAnson Huang struct clk *thermal_clk; 2103c94f17eSAnson Huang const struct thermal_soc_data *socdata; 211a2291badSTim Harvey const char *temp_grade; 212ca3de46bSShawn Guo }; 213ca3de46bSShawn Guo 2143c94f17eSAnson Huang static void imx_set_panic_temp(struct imx_thermal_data *data, 21517e8351aSSascha Hauer int panic_temp) 2163c94f17eSAnson Huang { 217f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 2183c94f17eSAnson Huang struct regmap *map = data->tempmon; 2193c94f17eSAnson Huang int critical_value; 2203c94f17eSAnson Huang 2213c94f17eSAnson Huang critical_value = (data->c2 - panic_temp) / data->c1; 222f085f672SAnson Huang 223f085f672SAnson Huang regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR, 224f085f672SAnson Huang soc_data->panic_alarm_mask); 225f085f672SAnson Huang regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET, 226f085f672SAnson Huang critical_value << soc_data->panic_alarm_shift); 2273c94f17eSAnson Huang } 2283c94f17eSAnson Huang 22937713a1eSPhilipp Zabel static void imx_set_alarm_temp(struct imx_thermal_data *data, 23017e8351aSSascha Hauer int alarm_temp) 23137713a1eSPhilipp Zabel { 23237713a1eSPhilipp Zabel struct regmap *map = data->tempmon; 233f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 23437713a1eSPhilipp Zabel int alarm_value; 23537713a1eSPhilipp Zabel 23637713a1eSPhilipp Zabel data->alarm_temp = alarm_temp; 237f085f672SAnson Huang 238f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) 239f085f672SAnson Huang alarm_value = alarm_temp / 1000 + data->c1 - 25; 240f085f672SAnson Huang else 241749e8be7SAnson Huang alarm_value = (data->c2 - alarm_temp) / data->c1; 242f085f672SAnson Huang 243f085f672SAnson Huang regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR, 244f085f672SAnson Huang soc_data->high_alarm_mask); 245f085f672SAnson Huang regmap_write(map, soc_data->high_alarm_ctrl + REG_SET, 246f085f672SAnson Huang alarm_value << soc_data->high_alarm_shift); 24737713a1eSPhilipp Zabel } 24837713a1eSPhilipp Zabel 24917e8351aSSascha Hauer static int imx_get_temp(struct thermal_zone_device *tz, int *temp) 250ca3de46bSShawn Guo { 251ca3de46bSShawn Guo struct imx_thermal_data *data = tz->devdata; 252f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 253ca3de46bSShawn Guo struct regmap *map = data->tempmon; 254ca3de46bSShawn Guo unsigned int n_meas; 255d92ed2c9SAndrzej Pietrasiewicz bool wait, run_measurement; 256ca3de46bSShawn Guo u32 val; 257ca3de46bSShawn Guo 258d92ed2c9SAndrzej Pietrasiewicz run_measurement = !data->irq_enabled; 259d92ed2c9SAndrzej Pietrasiewicz if (!run_measurement) { 26037713a1eSPhilipp Zabel /* Check if a measurement is currently in progress */ 261f085f672SAnson Huang regmap_read(map, soc_data->temp_data, &val); 262f085f672SAnson Huang wait = !(val & soc_data->temp_valid_mask); 26337713a1eSPhilipp Zabel } else { 264ca3de46bSShawn Guo /* 265ca3de46bSShawn Guo * Every time we measure the temperature, we will power on the 266ca3de46bSShawn Guo * temperature sensor, enable measurements, take a reading, 267ca3de46bSShawn Guo * disable measurements, power off the temperature sensor. 268ca3de46bSShawn Guo */ 269f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 270f085f672SAnson Huang soc_data->power_down_mask); 271f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 272f085f672SAnson Huang soc_data->measure_temp_mask); 273ca3de46bSShawn Guo 27437713a1eSPhilipp Zabel wait = true; 27537713a1eSPhilipp Zabel } 27637713a1eSPhilipp Zabel 277ca3de46bSShawn Guo /* 278ca3de46bSShawn Guo * According to the temp sensor designers, it may require up to ~17us 279ca3de46bSShawn Guo * to complete a measurement. 280ca3de46bSShawn Guo */ 28137713a1eSPhilipp Zabel if (wait) 282ca3de46bSShawn Guo usleep_range(20, 50); 283ca3de46bSShawn Guo 284f085f672SAnson Huang regmap_read(map, soc_data->temp_data, &val); 28537713a1eSPhilipp Zabel 286d92ed2c9SAndrzej Pietrasiewicz if (run_measurement) { 287f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 288f085f672SAnson Huang soc_data->measure_temp_mask); 289f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 290f085f672SAnson Huang soc_data->power_down_mask); 29137713a1eSPhilipp Zabel } 292ca3de46bSShawn Guo 293f085f672SAnson Huang if ((val & soc_data->temp_valid_mask) == 0) { 294ca3de46bSShawn Guo dev_dbg(&tz->device, "temp measurement never finished\n"); 295ca3de46bSShawn Guo return -EAGAIN; 296ca3de46bSShawn Guo } 297ca3de46bSShawn Guo 298f085f672SAnson Huang n_meas = (val & soc_data->temp_value_mask) 299f085f672SAnson Huang >> soc_data->temp_value_shift; 300ca3de46bSShawn Guo 301ae621557SLeonard Crestez /* See imx_init_calib() for formula derivation */ 302f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) 303f085f672SAnson Huang *temp = (n_meas - data->c1 + 25) * 1000; 304f085f672SAnson Huang else 305749e8be7SAnson Huang *temp = data->c2 - n_meas * data->c1; 306ca3de46bSShawn Guo 3073c94f17eSAnson Huang /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */ 3083c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6Q) { 3093c94f17eSAnson Huang if (data->alarm_temp == data->temp_passive && 3103c94f17eSAnson Huang *temp >= data->temp_passive) 31137713a1eSPhilipp Zabel imx_set_alarm_temp(data, data->temp_critical); 3123c94f17eSAnson Huang if (data->alarm_temp == data->temp_critical && 3133c94f17eSAnson Huang *temp < data->temp_passive) { 31437713a1eSPhilipp Zabel imx_set_alarm_temp(data, data->temp_passive); 31517e8351aSSascha Hauer dev_dbg(&tz->device, "thermal alarm off: T < %d\n", 31637713a1eSPhilipp Zabel data->alarm_temp / 1000); 31737713a1eSPhilipp Zabel } 3183c94f17eSAnson Huang } 31937713a1eSPhilipp Zabel 32037713a1eSPhilipp Zabel if (*temp != data->last_temp) { 32117e8351aSSascha Hauer dev_dbg(&tz->device, "millicelsius: %d\n", *temp); 32237713a1eSPhilipp Zabel data->last_temp = *temp; 32337713a1eSPhilipp Zabel } 32437713a1eSPhilipp Zabel 32537713a1eSPhilipp Zabel /* Reenable alarm IRQ if temperature below alarm temperature */ 32637713a1eSPhilipp Zabel if (!data->irq_enabled && *temp < data->alarm_temp) { 32737713a1eSPhilipp Zabel data->irq_enabled = true; 32837713a1eSPhilipp Zabel enable_irq(data->irq); 329ca3de46bSShawn Guo } 330ca3de46bSShawn Guo 331ca3de46bSShawn Guo return 0; 332ca3de46bSShawn Guo } 333ca3de46bSShawn Guo 334f5e50bf4SAndrzej Pietrasiewicz static int imx_change_mode(struct thermal_zone_device *tz, 335ca3de46bSShawn Guo enum thermal_device_mode mode) 336ca3de46bSShawn Guo { 337ca3de46bSShawn Guo struct imx_thermal_data *data = tz->devdata; 33837713a1eSPhilipp Zabel struct regmap *map = data->tempmon; 339f085f672SAnson Huang const struct thermal_soc_data *soc_data = data->socdata; 340ca3de46bSShawn Guo 341ca3de46bSShawn Guo if (mode == THERMAL_DEVICE_ENABLED) { 342f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 343f085f672SAnson Huang soc_data->power_down_mask); 344f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 345f085f672SAnson Huang soc_data->measure_temp_mask); 34637713a1eSPhilipp Zabel 34737713a1eSPhilipp Zabel if (!data->irq_enabled) { 34837713a1eSPhilipp Zabel data->irq_enabled = true; 34937713a1eSPhilipp Zabel enable_irq(data->irq); 35037713a1eSPhilipp Zabel } 351ca3de46bSShawn Guo } else { 352f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_CLR, 353f085f672SAnson Huang soc_data->measure_temp_mask); 354f085f672SAnson Huang regmap_write(map, soc_data->sensor_ctrl + REG_SET, 355f085f672SAnson Huang soc_data->power_down_mask); 35637713a1eSPhilipp Zabel 35737713a1eSPhilipp Zabel if (data->irq_enabled) { 35837713a1eSPhilipp Zabel disable_irq(data->irq); 35937713a1eSPhilipp Zabel data->irq_enabled = false; 36037713a1eSPhilipp Zabel } 361ca3de46bSShawn Guo } 362ca3de46bSShawn Guo 363ca3de46bSShawn Guo return 0; 364ca3de46bSShawn Guo } 365ca3de46bSShawn Guo 366ca3de46bSShawn Guo static int imx_get_trip_type(struct thermal_zone_device *tz, int trip, 367ca3de46bSShawn Guo enum thermal_trip_type *type) 368ca3de46bSShawn Guo { 369ca3de46bSShawn Guo *type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE : 370ca3de46bSShawn Guo THERMAL_TRIP_CRITICAL; 371ca3de46bSShawn Guo return 0; 372ca3de46bSShawn Guo } 373ca3de46bSShawn Guo 37417e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp) 375ca3de46bSShawn Guo { 376017e5142SPhilipp Zabel struct imx_thermal_data *data = tz->devdata; 377017e5142SPhilipp Zabel 378017e5142SPhilipp Zabel *temp = data->temp_critical; 379ca3de46bSShawn Guo return 0; 380ca3de46bSShawn Guo } 381ca3de46bSShawn Guo 382ca3de46bSShawn Guo static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip, 38317e8351aSSascha Hauer int *temp) 384ca3de46bSShawn Guo { 385017e5142SPhilipp Zabel struct imx_thermal_data *data = tz->devdata; 386017e5142SPhilipp Zabel 387017e5142SPhilipp Zabel *temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive : 388017e5142SPhilipp Zabel data->temp_critical; 389017e5142SPhilipp Zabel return 0; 390017e5142SPhilipp Zabel } 391017e5142SPhilipp Zabel 392017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, 39317e8351aSSascha Hauer int temp) 394017e5142SPhilipp Zabel { 395017e5142SPhilipp Zabel struct imx_thermal_data *data = tz->devdata; 396017e5142SPhilipp Zabel 397a2291badSTim Harvey /* do not allow changing critical threshold */ 398017e5142SPhilipp Zabel if (trip == IMX_TRIP_CRITICAL) 399017e5142SPhilipp Zabel return -EPERM; 400017e5142SPhilipp Zabel 401a2291badSTim Harvey /* do not allow passive to be set higher than critical */ 402a2291badSTim Harvey if (temp < 0 || temp > data->temp_critical) 403017e5142SPhilipp Zabel return -EINVAL; 404017e5142SPhilipp Zabel 405017e5142SPhilipp Zabel data->temp_passive = temp; 406017e5142SPhilipp Zabel 40737713a1eSPhilipp Zabel imx_set_alarm_temp(data, temp); 40837713a1eSPhilipp Zabel 409ca3de46bSShawn Guo return 0; 410ca3de46bSShawn Guo } 411ca3de46bSShawn Guo 412ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz, 413ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 414ca3de46bSShawn Guo { 415ca3de46bSShawn Guo int ret; 416ca3de46bSShawn Guo 417ca3de46bSShawn Guo ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, 418ca3de46bSShawn Guo THERMAL_NO_LIMIT, 4196cd9e9f6SKapileshwar Singh THERMAL_NO_LIMIT, 4206cd9e9f6SKapileshwar Singh THERMAL_WEIGHT_DEFAULT); 421ca3de46bSShawn Guo if (ret) { 422ca3de46bSShawn Guo dev_err(&tz->device, 423ca3de46bSShawn Guo "binding zone %s with cdev %s failed:%d\n", 424ca3de46bSShawn Guo tz->type, cdev->type, ret); 425ca3de46bSShawn Guo return ret; 426ca3de46bSShawn Guo } 427ca3de46bSShawn Guo 428ca3de46bSShawn Guo return 0; 429ca3de46bSShawn Guo } 430ca3de46bSShawn Guo 431ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz, 432ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 433ca3de46bSShawn Guo { 434ca3de46bSShawn Guo int ret; 435ca3de46bSShawn Guo 436ca3de46bSShawn Guo ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); 437ca3de46bSShawn Guo if (ret) { 438ca3de46bSShawn Guo dev_err(&tz->device, 439ca3de46bSShawn Guo "unbinding zone %s with cdev %s failed:%d\n", 440ca3de46bSShawn Guo tz->type, cdev->type, ret); 441ca3de46bSShawn Guo return ret; 442ca3de46bSShawn Guo } 443ca3de46bSShawn Guo 444ca3de46bSShawn Guo return 0; 445ca3de46bSShawn Guo } 446ca3de46bSShawn Guo 447cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = { 448ca3de46bSShawn Guo .bind = imx_bind, 449ca3de46bSShawn Guo .unbind = imx_unbind, 450ca3de46bSShawn Guo .get_temp = imx_get_temp, 451f5e50bf4SAndrzej Pietrasiewicz .change_mode = imx_change_mode, 452ca3de46bSShawn Guo .get_trip_type = imx_get_trip_type, 453ca3de46bSShawn Guo .get_trip_temp = imx_get_trip_temp, 454ca3de46bSShawn Guo .get_crit_temp = imx_get_crit_temp, 455017e5142SPhilipp Zabel .set_trip_temp = imx_set_trip_temp, 456ca3de46bSShawn Guo }; 457ca3de46bSShawn Guo 458e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1) 459ca3de46bSShawn Guo { 460ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 4614e5f61caSUwe Kleine-König int n1; 462749e8be7SAnson Huang u64 temp64; 463ca3de46bSShawn Guo 464e4bb2240SUwe Kleine-König if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) { 465ca3de46bSShawn Guo dev_err(&pdev->dev, "invalid sensor calibration data\n"); 466ca3de46bSShawn Guo return -EINVAL; 467ca3de46bSShawn Guo } 468ca3de46bSShawn Guo 469ca3de46bSShawn Guo /* 470f085f672SAnson Huang * On i.MX7D, we only use the calibration data at 25C to get the temp, 471f085f672SAnson Huang * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C. 472f085f672SAnson Huang */ 473f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) { 474f085f672SAnson Huang data->c1 = (ocotp_ana1 >> 9) & 0x1ff; 475f085f672SAnson Huang return 0; 476f085f672SAnson Huang } 477f085f672SAnson Huang 478f085f672SAnson Huang /* 479c5bbdb4bSUwe Kleine-König * The sensor is calibrated at 25 °C (aka T1) and the value measured 480c5bbdb4bSUwe Kleine-König * (aka N1) at this temperature is provided in bits [31:20] in the 481c5bbdb4bSUwe Kleine-König * i.MX's OCOTP value ANA1. 482c5bbdb4bSUwe Kleine-König * To find the actual temperature T, the following formula has to be used 483c5bbdb4bSUwe Kleine-König * when reading value n from the sensor: 484c5bbdb4bSUwe Kleine-König * 4854e5f61caSUwe Kleine-König * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C 4864e5f61caSUwe Kleine-König * = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C 4874e5f61caSUwe Kleine-König * = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C 488c5bbdb4bSUwe Kleine-König * = c2 - c1 * N 489c5bbdb4bSUwe Kleine-König * 490c5bbdb4bSUwe Kleine-König * with 491c5bbdb4bSUwe Kleine-König * 4924e5f61caSUwe Kleine-König * T1' = 28.580661 °C 4934e5f61caSUwe Kleine-König * c1 = 1 / (0.0015423 * N1 - 0.4297157) °C 4944e5f61caSUwe Kleine-König * c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C 4954e5f61caSUwe Kleine-König * = T1' + N1 * c1 496ca3de46bSShawn Guo */ 497e4bb2240SUwe Kleine-König n1 = ocotp_ana1 >> 20; 498ca3de46bSShawn Guo 4994e5f61caSUwe Kleine-König temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */ 500c5bbdb4bSUwe Kleine-König temp64 *= 1000; /* to get result in °mC */ 5014e5f61caSUwe Kleine-König do_div(temp64, 15423 * n1 - 4148468); 502749e8be7SAnson Huang data->c1 = temp64; 5034e5f61caSUwe Kleine-König data->c2 = n1 * data->c1 + 28581; 504ca3de46bSShawn Guo 505ae621557SLeonard Crestez return 0; 506a2291badSTim Harvey } 507a2291badSTim Harvey 508e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0) 509ae621557SLeonard Crestez { 510ae621557SLeonard Crestez struct imx_thermal_data *data = platform_get_drvdata(pdev); 511ae621557SLeonard Crestez 512a2291badSTim Harvey /* The maximum die temp is specified by the Temperature Grade */ 513e4bb2240SUwe Kleine-König switch ((ocotp_mem0 >> 6) & 0x3) { 514339d7492SUwe Kleine-König case 0: /* Commercial (0 to 95 °C) */ 515a2291badSTim Harvey data->temp_grade = "Commercial"; 516a2291badSTim Harvey data->temp_max = 95000; 517a2291badSTim Harvey break; 518339d7492SUwe Kleine-König case 1: /* Extended Commercial (-20 °C to 105 °C) */ 519a2291badSTim Harvey data->temp_grade = "Extended Commercial"; 520a2291badSTim Harvey data->temp_max = 105000; 521a2291badSTim Harvey break; 522339d7492SUwe Kleine-König case 2: /* Industrial (-40 °C to 105 °C) */ 523a2291badSTim Harvey data->temp_grade = "Industrial"; 524a2291badSTim Harvey data->temp_max = 105000; 525a2291badSTim Harvey break; 526339d7492SUwe Kleine-König case 3: /* Automotive (-40 °C to 125 °C) */ 527a2291badSTim Harvey data->temp_grade = "Automotive"; 528a2291badSTim Harvey data->temp_max = 125000; 529a2291badSTim Harvey break; 530a2291badSTim Harvey } 531017e5142SPhilipp Zabel 532017e5142SPhilipp Zabel /* 533339d7492SUwe Kleine-König * Set the critical trip point at 5 °C under max 534339d7492SUwe Kleine-König * Set the passive trip point at 10 °C under max (changeable via sysfs) 535017e5142SPhilipp Zabel */ 536a2291badSTim Harvey data->temp_critical = data->temp_max - (1000 * 5); 537a2291badSTim Harvey data->temp_passive = data->temp_max - (1000 * 10); 538ae621557SLeonard Crestez } 539ae621557SLeonard Crestez 540ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev) 541ae621557SLeonard Crestez { 542ae621557SLeonard Crestez struct regmap *map; 543ae621557SLeonard Crestez int ret; 544ae621557SLeonard Crestez u32 val; 545ae621557SLeonard Crestez 546ae621557SLeonard Crestez map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, 547ae621557SLeonard Crestez "fsl,tempmon-data"); 548ae621557SLeonard Crestez if (IS_ERR(map)) { 549ae621557SLeonard Crestez ret = PTR_ERR(map); 550ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); 551ae621557SLeonard Crestez return ret; 552ae621557SLeonard Crestez } 553ae621557SLeonard Crestez 554ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_ANA1, &val); 555ae621557SLeonard Crestez if (ret) { 556ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 557ae621557SLeonard Crestez return ret; 558ae621557SLeonard Crestez } 559ae621557SLeonard Crestez ret = imx_init_calib(pdev, val); 560ae621557SLeonard Crestez if (ret) 561ae621557SLeonard Crestez return ret; 562ae621557SLeonard Crestez 563ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_MEM0, &val); 564ae621557SLeonard Crestez if (ret) { 565ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 566ae621557SLeonard Crestez return ret; 567ae621557SLeonard Crestez } 568ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 569ae621557SLeonard Crestez 570ae621557SLeonard Crestez return 0; 571ae621557SLeonard Crestez } 572ae621557SLeonard Crestez 573ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev) 574ae621557SLeonard Crestez { 575ae621557SLeonard Crestez int ret; 576ae621557SLeonard Crestez u32 val; 577ae621557SLeonard Crestez 578ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val); 579ae621557SLeonard Crestez if (ret) 580ae621557SLeonard Crestez return ret; 581be926ceeSJean-Christophe Dubois 582be926ceeSJean-Christophe Dubois ret = imx_init_calib(pdev, val); 583be926ceeSJean-Christophe Dubois if (ret) 584be926ceeSJean-Christophe Dubois return ret; 585ae621557SLeonard Crestez 586ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val); 587ae621557SLeonard Crestez if (ret) 588ae621557SLeonard Crestez return ret; 589ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 590017e5142SPhilipp Zabel 591ca3de46bSShawn Guo return 0; 592ca3de46bSShawn Guo } 593ca3de46bSShawn Guo 59437713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev) 59537713a1eSPhilipp Zabel { 59637713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 59737713a1eSPhilipp Zabel 59837713a1eSPhilipp Zabel disable_irq_nosync(irq); 59937713a1eSPhilipp Zabel data->irq_enabled = false; 60037713a1eSPhilipp Zabel 60137713a1eSPhilipp Zabel return IRQ_WAKE_THREAD; 60237713a1eSPhilipp Zabel } 60337713a1eSPhilipp Zabel 60437713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) 60537713a1eSPhilipp Zabel { 60637713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 60737713a1eSPhilipp Zabel 60817e8351aSSascha Hauer dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n", 60937713a1eSPhilipp Zabel data->alarm_temp / 1000); 61037713a1eSPhilipp Zabel 6110e70f466SSrinivas Pandruvada thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED); 61237713a1eSPhilipp Zabel 61337713a1eSPhilipp Zabel return IRQ_HANDLED; 61437713a1eSPhilipp Zabel } 61537713a1eSPhilipp Zabel 6163c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = { 6173c94f17eSAnson Huang { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, }, 6183c94f17eSAnson Huang { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, }, 619f085f672SAnson Huang { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, }, 6203c94f17eSAnson Huang { /* end */ } 6213c94f17eSAnson Huang }; 6223c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match); 6233c94f17eSAnson Huang 624c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ 625a1d00154SBastian Stender /* 626a1d00154SBastian Stender * Create cooling device in case no #cooling-cells property is available in 627a1d00154SBastian Stender * CPU node 628a1d00154SBastian Stender */ 629a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data) 630a1d00154SBastian Stender { 631c589c566SAnson Huang struct device_node *np; 632b45fd13bSAnson Huang int ret = 0; 633a1d00154SBastian Stender 634c589c566SAnson Huang data->policy = cpufreq_cpu_get(0); 635c589c566SAnson Huang if (!data->policy) { 636c589c566SAnson Huang pr_debug("%s: CPUFreq policy not found\n", __func__); 637c589c566SAnson Huang return -EPROBE_DEFER; 638c589c566SAnson Huang } 639c589c566SAnson Huang 640c589c566SAnson Huang np = of_get_cpu_node(data->policy->cpu, NULL); 641c589c566SAnson Huang 642a1d00154SBastian Stender if (!np || !of_find_property(np, "#cooling-cells", NULL)) { 643a1d00154SBastian Stender data->cdev = cpufreq_cooling_register(data->policy); 644a1d00154SBastian Stender if (IS_ERR(data->cdev)) { 645a1d00154SBastian Stender ret = PTR_ERR(data->cdev); 646a1d00154SBastian Stender cpufreq_cpu_put(data->policy); 647a1d00154SBastian Stender } 648a1d00154SBastian Stender } 649a1d00154SBastian Stender 650b45fd13bSAnson Huang of_node_put(np); 651b45fd13bSAnson Huang 652b45fd13bSAnson Huang return ret; 653a1d00154SBastian Stender } 654a1d00154SBastian Stender 655c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data) 656c589c566SAnson Huang { 657c589c566SAnson Huang cpufreq_cooling_unregister(data->cdev); 658c589c566SAnson Huang cpufreq_cpu_put(data->policy); 659c589c566SAnson Huang } 660c589c566SAnson Huang 661c589c566SAnson Huang #else 662c589c566SAnson Huang 663c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data) 664c589c566SAnson Huang { 665c589c566SAnson Huang return 0; 666c589c566SAnson Huang } 667c589c566SAnson Huang 668c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data) 669c589c566SAnson Huang { 670c589c566SAnson Huang } 671c589c566SAnson Huang #endif 672c589c566SAnson Huang 673ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev) 674ca3de46bSShawn Guo { 675ca3de46bSShawn Guo struct imx_thermal_data *data; 676ca3de46bSShawn Guo struct regmap *map; 67737713a1eSPhilipp Zabel int measure_freq; 678ca3de46bSShawn Guo int ret; 679ca3de46bSShawn Guo 680ca3de46bSShawn Guo data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 681ca3de46bSShawn Guo if (!data) 682ca3de46bSShawn Guo return -ENOMEM; 683ca3de46bSShawn Guo 684ca3de46bSShawn Guo map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); 685ca3de46bSShawn Guo if (IS_ERR(map)) { 686ca3de46bSShawn Guo ret = PTR_ERR(map); 687ca3de46bSShawn Guo dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); 688ca3de46bSShawn Guo return ret; 689ca3de46bSShawn Guo } 690ca3de46bSShawn Guo data->tempmon = map; 691ca3de46bSShawn Guo 692829bc78aSCorentin LABBE data->socdata = of_device_get_match_data(&pdev->dev); 6938b051ec3SShailendra Verma if (!data->socdata) { 6948b051ec3SShailendra Verma dev_err(&pdev->dev, "no device match found\n"); 6958b051ec3SShailendra Verma return -ENODEV; 6968b051ec3SShailendra Verma } 6973c94f17eSAnson Huang 6983c94f17eSAnson Huang /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */ 6993c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) { 700f085f672SAnson Huang regmap_write(map, IMX6_MISC1 + REG_CLR, 701f085f672SAnson Huang IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW 702f085f672SAnson Huang | IMX6_MISC1_IRQ_TEMPPANIC); 7033c94f17eSAnson Huang /* 7043c94f17eSAnson Huang * reset value of LOW ALARM is incorrect, set it to lowest 7053c94f17eSAnson Huang * value to avoid false trigger of low alarm. 7063c94f17eSAnson Huang */ 707f085f672SAnson Huang regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET, 708f085f672SAnson Huang data->socdata->low_alarm_mask); 7093c94f17eSAnson Huang } 7103c94f17eSAnson Huang 71137713a1eSPhilipp Zabel data->irq = platform_get_irq(pdev, 0); 71237713a1eSPhilipp Zabel if (data->irq < 0) 71337713a1eSPhilipp Zabel return data->irq; 71437713a1eSPhilipp Zabel 715ca3de46bSShawn Guo platform_set_drvdata(pdev, data); 716ca3de46bSShawn Guo 717ae621557SLeonard Crestez if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) { 718ae621557SLeonard Crestez ret = imx_init_from_nvmem_cells(pdev); 7195f3c0200SAnson Huang if (ret) 7205f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret, 7215f3c0200SAnson Huang "failed to init from nvmem\n"); 722ae621557SLeonard Crestez } else { 723ae621557SLeonard Crestez ret = imx_init_from_tempmon_data(pdev); 724ae621557SLeonard Crestez if (ret) { 725337a4aecSAnson Huang dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n"); 726ae621557SLeonard Crestez return ret; 727ae621557SLeonard Crestez } 728ca3de46bSShawn Guo } 729ca3de46bSShawn Guo 730ca3de46bSShawn Guo /* Make sure sensor is in known good state for measurements */ 731f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 732f085f672SAnson Huang data->socdata->power_down_mask); 733f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 734f085f672SAnson Huang data->socdata->measure_temp_mask); 735f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, 736f085f672SAnson Huang data->socdata->measure_freq_mask); 737f085f672SAnson Huang if (data->socdata->version != TEMPMON_IMX7D) 738f085f672SAnson Huang regmap_write(map, IMX6_MISC0 + REG_SET, 739f085f672SAnson Huang IMX6_MISC0_REFTOP_SELBIASOFF); 740f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 741f085f672SAnson Huang data->socdata->power_down_mask); 742ca3de46bSShawn Guo 743a1d00154SBastian Stender ret = imx_thermal_register_legacy_cooling(data); 7445f3c0200SAnson Huang if (ret) 7455f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret, 7465f3c0200SAnson Huang "failed to register cpufreq cooling device\n"); 747ca3de46bSShawn Guo 74890a21ff5SHeiner Kallweit data->thermal_clk = devm_clk_get(&pdev->dev, NULL); 74990a21ff5SHeiner Kallweit if (IS_ERR(data->thermal_clk)) { 75090a21ff5SHeiner Kallweit ret = PTR_ERR(data->thermal_clk); 75190a21ff5SHeiner Kallweit if (ret != -EPROBE_DEFER) 75290a21ff5SHeiner Kallweit dev_err(&pdev->dev, 75390a21ff5SHeiner Kallweit "failed to get thermal clk: %d\n", ret); 754c589c566SAnson Huang goto legacy_cleanup; 75590a21ff5SHeiner Kallweit } 75690a21ff5SHeiner Kallweit 75790a21ff5SHeiner Kallweit /* 75890a21ff5SHeiner Kallweit * Thermal sensor needs clk on to get correct value, normally 75990a21ff5SHeiner Kallweit * we should enable its clk before taking measurement and disable 76090a21ff5SHeiner Kallweit * clk after measurement is done, but if alarm function is enabled, 76190a21ff5SHeiner Kallweit * hardware will auto measure the temperature periodically, so we 76290a21ff5SHeiner Kallweit * need to keep the clk always on for alarm function. 76390a21ff5SHeiner Kallweit */ 76490a21ff5SHeiner Kallweit ret = clk_prepare_enable(data->thermal_clk); 76590a21ff5SHeiner Kallweit if (ret) { 76690a21ff5SHeiner Kallweit dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret); 767c589c566SAnson Huang goto legacy_cleanup; 76890a21ff5SHeiner Kallweit } 76990a21ff5SHeiner Kallweit 770ca3de46bSShawn Guo data->tz = thermal_zone_device_register("imx_thermal_zone", 771017e5142SPhilipp Zabel IMX_TRIP_NUM, 772017e5142SPhilipp Zabel BIT(IMX_TRIP_PASSIVE), data, 773ca3de46bSShawn Guo &imx_tz_ops, NULL, 774ca3de46bSShawn Guo IMX_PASSIVE_DELAY, 775ca3de46bSShawn Guo IMX_POLLING_DELAY); 776ca3de46bSShawn Guo if (IS_ERR(data->tz)) { 777ca3de46bSShawn Guo ret = PTR_ERR(data->tz); 778ca3de46bSShawn Guo dev_err(&pdev->dev, 779ca3de46bSShawn Guo "failed to register thermal zone device %d\n", ret); 780b6ad3981SAnson Huang goto clk_disable; 781ca3de46bSShawn Guo } 782ca3de46bSShawn Guo 783a2291badSTim Harvey dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC" 784a2291badSTim Harvey " critical:%dC passive:%dC\n", data->temp_grade, 785a2291badSTim Harvey data->temp_max / 1000, data->temp_critical / 1000, 786a2291badSTim Harvey data->temp_passive / 1000); 787a2291badSTim Harvey 78837713a1eSPhilipp Zabel /* Enable measurements at ~ 10 Hz */ 789f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, 790f085f672SAnson Huang data->socdata->measure_freq_mask); 79137713a1eSPhilipp Zabel measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ 792f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET, 793f085f672SAnson Huang measure_freq << data->socdata->measure_freq_shift); 79437713a1eSPhilipp Zabel imx_set_alarm_temp(data, data->temp_passive); 7953c94f17eSAnson Huang 7963c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) 7973c94f17eSAnson Huang imx_set_panic_temp(data, data->temp_critical); 7983c94f17eSAnson Huang 799f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 800f085f672SAnson Huang data->socdata->power_down_mask); 801f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 802f085f672SAnson Huang data->socdata->measure_temp_mask); 80337713a1eSPhilipp Zabel 804cf1ba1d7SMikhail Lappo data->irq_enabled = true; 8057f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_enable(data->tz); 8067f4957beSAndrzej Pietrasiewicz if (ret) 8077f4957beSAndrzej Pietrasiewicz goto thermal_zone_unregister; 808cf1ba1d7SMikhail Lappo 80984866ee5SBai Ping ret = devm_request_threaded_irq(&pdev->dev, data->irq, 81084866ee5SBai Ping imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, 81184866ee5SBai Ping 0, "imx_thermal", data); 81284866ee5SBai Ping if (ret < 0) { 81384866ee5SBai Ping dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); 814b6ad3981SAnson Huang goto thermal_zone_unregister; 81584866ee5SBai Ping } 81684866ee5SBai Ping 817ca3de46bSShawn Guo return 0; 818b6ad3981SAnson Huang 819b6ad3981SAnson Huang thermal_zone_unregister: 820b6ad3981SAnson Huang thermal_zone_device_unregister(data->tz); 821b6ad3981SAnson Huang clk_disable: 822b6ad3981SAnson Huang clk_disable_unprepare(data->thermal_clk); 823c589c566SAnson Huang legacy_cleanup: 824c589c566SAnson Huang imx_thermal_unregister_legacy_cooling(data); 825b6ad3981SAnson Huang 826b6ad3981SAnson Huang return ret; 827ca3de46bSShawn Guo } 828ca3de46bSShawn Guo 829ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev) 830ca3de46bSShawn Guo { 831ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 83237713a1eSPhilipp Zabel struct regmap *map = data->tempmon; 83337713a1eSPhilipp Zabel 83437713a1eSPhilipp Zabel /* Disable measurements */ 835f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 836f085f672SAnson Huang data->socdata->power_down_mask); 837329fe7b1SAnson Huang if (!IS_ERR(data->thermal_clk)) 838329fe7b1SAnson Huang clk_disable_unprepare(data->thermal_clk); 839ca3de46bSShawn Guo 840ca3de46bSShawn Guo thermal_zone_device_unregister(data->tz); 8419db11010SAnson Huang imx_thermal_unregister_legacy_cooling(data); 842ca3de46bSShawn Guo 843ca3de46bSShawn Guo return 0; 844ca3de46bSShawn Guo } 845ca3de46bSShawn Guo 846b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev) 847ca3de46bSShawn Guo { 848ca3de46bSShawn Guo struct imx_thermal_data *data = dev_get_drvdata(dev); 8497f4957beSAndrzej Pietrasiewicz int ret; 850ca3de46bSShawn Guo 851ca3de46bSShawn Guo /* 852b46cce59SAnson Huang * Need to disable thermal sensor, otherwise, when thermal core 853b46cce59SAnson Huang * try to get temperature before thermal sensor resume, a wrong 854b46cce59SAnson Huang * temperature will be read as the thermal sensor is powered 855f5e50bf4SAndrzej Pietrasiewicz * down. This is done in change_mode() operation called from 8567f4957beSAndrzej Pietrasiewicz * thermal_zone_device_disable() 857ca3de46bSShawn Guo */ 8587f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_disable(data->tz); 8597f4957beSAndrzej Pietrasiewicz if (ret) 8607f4957beSAndrzej Pietrasiewicz return ret; 861d26eef8bSAnson Huang clk_disable_unprepare(data->thermal_clk); 862ca3de46bSShawn Guo 863ca3de46bSShawn Guo return 0; 864ca3de46bSShawn Guo } 865ca3de46bSShawn Guo 866b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev) 867ca3de46bSShawn Guo { 868b46cce59SAnson Huang struct imx_thermal_data *data = dev_get_drvdata(dev); 869e3bdc8d7SArvind Yadav int ret; 870b46cce59SAnson Huang 871e3bdc8d7SArvind Yadav ret = clk_prepare_enable(data->thermal_clk); 872e3bdc8d7SArvind Yadav if (ret) 873e3bdc8d7SArvind Yadav return ret; 874b46cce59SAnson Huang /* Enabled thermal sensor after resume */ 8757f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_enable(data->tz); 8767f4957beSAndrzej Pietrasiewicz if (ret) 8777f4957beSAndrzej Pietrasiewicz return ret; 878b46cce59SAnson Huang 879ca3de46bSShawn Guo return 0; 880ca3de46bSShawn Guo } 881ca3de46bSShawn Guo 882ca3de46bSShawn Guo static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops, 883ca3de46bSShawn Guo imx_thermal_suspend, imx_thermal_resume); 884ca3de46bSShawn Guo 885ca3de46bSShawn Guo static struct platform_driver imx_thermal = { 886ca3de46bSShawn Guo .driver = { 887ca3de46bSShawn Guo .name = "imx_thermal", 888ca3de46bSShawn Guo .pm = &imx_thermal_pm_ops, 889ca3de46bSShawn Guo .of_match_table = of_imx_thermal_match, 890ca3de46bSShawn Guo }, 891ca3de46bSShawn Guo .probe = imx_thermal_probe, 892ca3de46bSShawn Guo .remove = imx_thermal_remove, 893ca3de46bSShawn Guo }; 894ca3de46bSShawn Guo module_platform_driver(imx_thermal); 895ca3de46bSShawn Guo 896ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc."); 897ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); 898ca3de46bSShawn Guo MODULE_LICENSE("GPL v2"); 899ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal"); 900