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> 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 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 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 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 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 33317e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp) 334ca3de46bSShawn Guo { 33530233a22SDaniel Lezcano *temp = trips[IMX_TRIP_CRITICAL].temperature; 336017e5142SPhilipp Zabel 337017e5142SPhilipp Zabel return 0; 338017e5142SPhilipp Zabel } 339017e5142SPhilipp Zabel 340017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, 34117e8351aSSascha Hauer int temp) 342017e5142SPhilipp Zabel { 3435f68d078SDaniel Lezcano struct imx_thermal_data *data = thermal_zone_device_priv(tz); 3444cf2ddf1SOleksij Rempel int ret; 3454cf2ddf1SOleksij Rempel 3464cf2ddf1SOleksij Rempel ret = pm_runtime_resume_and_get(data->dev); 3474cf2ddf1SOleksij Rempel if (ret < 0) 3484cf2ddf1SOleksij Rempel return ret; 349017e5142SPhilipp Zabel 350a2291badSTim Harvey /* do not allow changing critical threshold */ 351017e5142SPhilipp Zabel if (trip == IMX_TRIP_CRITICAL) 352017e5142SPhilipp Zabel return -EPERM; 353017e5142SPhilipp Zabel 354a2291badSTim Harvey /* do not allow passive to be set higher than critical */ 35530233a22SDaniel Lezcano if (temp < 0 || temp > trips[IMX_TRIP_CRITICAL].temperature) 356017e5142SPhilipp Zabel return -EINVAL; 357017e5142SPhilipp Zabel 35830233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature = temp; 359017e5142SPhilipp Zabel 36037713a1eSPhilipp Zabel imx_set_alarm_temp(data, temp); 36137713a1eSPhilipp Zabel 3624cf2ddf1SOleksij Rempel pm_runtime_put(data->dev); 3634cf2ddf1SOleksij Rempel 364ca3de46bSShawn Guo return 0; 365ca3de46bSShawn Guo } 366ca3de46bSShawn Guo 367ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz, 368ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 369ca3de46bSShawn Guo { 370dec07d39SDaniel Lezcano return thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, 371ca3de46bSShawn Guo THERMAL_NO_LIMIT, 3726cd9e9f6SKapileshwar Singh THERMAL_NO_LIMIT, 3736cd9e9f6SKapileshwar Singh THERMAL_WEIGHT_DEFAULT); 374ca3de46bSShawn Guo } 375ca3de46bSShawn Guo 376ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz, 377ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 378ca3de46bSShawn Guo { 379dec07d39SDaniel Lezcano return thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); 380ca3de46bSShawn Guo } 381ca3de46bSShawn Guo 382cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = { 383ca3de46bSShawn Guo .bind = imx_bind, 384ca3de46bSShawn Guo .unbind = imx_unbind, 385ca3de46bSShawn Guo .get_temp = imx_get_temp, 386f5e50bf4SAndrzej Pietrasiewicz .change_mode = imx_change_mode, 387ca3de46bSShawn Guo .get_crit_temp = imx_get_crit_temp, 388017e5142SPhilipp Zabel .set_trip_temp = imx_set_trip_temp, 389ca3de46bSShawn Guo }; 390ca3de46bSShawn Guo 391e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1) 392ca3de46bSShawn Guo { 393ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 3944e5f61caSUwe Kleine-König int n1; 395749e8be7SAnson Huang u64 temp64; 396ca3de46bSShawn Guo 397e4bb2240SUwe Kleine-König if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) { 398ca3de46bSShawn Guo dev_err(&pdev->dev, "invalid sensor calibration data\n"); 399ca3de46bSShawn Guo return -EINVAL; 400ca3de46bSShawn Guo } 401ca3de46bSShawn Guo 402ca3de46bSShawn Guo /* 403f085f672SAnson Huang * On i.MX7D, we only use the calibration data at 25C to get the temp, 404f085f672SAnson Huang * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C. 405f085f672SAnson Huang */ 406f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) { 407f085f672SAnson Huang data->c1 = (ocotp_ana1 >> 9) & 0x1ff; 408f085f672SAnson Huang return 0; 409f085f672SAnson Huang } 410f085f672SAnson Huang 411f085f672SAnson Huang /* 412c5bbdb4bSUwe Kleine-König * The sensor is calibrated at 25 °C (aka T1) and the value measured 413c5bbdb4bSUwe Kleine-König * (aka N1) at this temperature is provided in bits [31:20] in the 414c5bbdb4bSUwe Kleine-König * i.MX's OCOTP value ANA1. 415c5bbdb4bSUwe Kleine-König * To find the actual temperature T, the following formula has to be used 416c5bbdb4bSUwe Kleine-König * when reading value n from the sensor: 417c5bbdb4bSUwe Kleine-König * 4184e5f61caSUwe Kleine-König * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C 4194e5f61caSUwe Kleine-König * = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C 4204e5f61caSUwe Kleine-König * = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C 421c5bbdb4bSUwe Kleine-König * = c2 - c1 * N 422c5bbdb4bSUwe Kleine-König * 423c5bbdb4bSUwe Kleine-König * with 424c5bbdb4bSUwe Kleine-König * 4254e5f61caSUwe Kleine-König * T1' = 28.580661 °C 4264e5f61caSUwe Kleine-König * c1 = 1 / (0.0015423 * N1 - 0.4297157) °C 4274e5f61caSUwe Kleine-König * c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C 4284e5f61caSUwe Kleine-König * = T1' + N1 * c1 429ca3de46bSShawn Guo */ 430e4bb2240SUwe Kleine-König n1 = ocotp_ana1 >> 20; 431ca3de46bSShawn Guo 4324e5f61caSUwe Kleine-König temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */ 433c5bbdb4bSUwe Kleine-König temp64 *= 1000; /* to get result in °mC */ 4344e5f61caSUwe Kleine-König do_div(temp64, 15423 * n1 - 4148468); 435749e8be7SAnson Huang data->c1 = temp64; 4364e5f61caSUwe Kleine-König data->c2 = n1 * data->c1 + 28581; 437ca3de46bSShawn Guo 438ae621557SLeonard Crestez return 0; 439a2291badSTim Harvey } 440a2291badSTim Harvey 441e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0) 442ae621557SLeonard Crestez { 443ae621557SLeonard Crestez struct imx_thermal_data *data = platform_get_drvdata(pdev); 444ae621557SLeonard Crestez 445a2291badSTim Harvey /* The maximum die temp is specified by the Temperature Grade */ 446e4bb2240SUwe Kleine-König switch ((ocotp_mem0 >> 6) & 0x3) { 447339d7492SUwe Kleine-König case 0: /* Commercial (0 to 95 °C) */ 448a2291badSTim Harvey data->temp_grade = "Commercial"; 449a2291badSTim Harvey data->temp_max = 95000; 450a2291badSTim Harvey break; 451339d7492SUwe Kleine-König case 1: /* Extended Commercial (-20 °C to 105 °C) */ 452a2291badSTim Harvey data->temp_grade = "Extended Commercial"; 453a2291badSTim Harvey data->temp_max = 105000; 454a2291badSTim Harvey break; 455339d7492SUwe Kleine-König case 2: /* Industrial (-40 °C to 105 °C) */ 456a2291badSTim Harvey data->temp_grade = "Industrial"; 457a2291badSTim Harvey data->temp_max = 105000; 458a2291badSTim Harvey break; 459339d7492SUwe Kleine-König case 3: /* Automotive (-40 °C to 125 °C) */ 460a2291badSTim Harvey data->temp_grade = "Automotive"; 461a2291badSTim Harvey data->temp_max = 125000; 462a2291badSTim Harvey break; 463a2291badSTim Harvey } 464017e5142SPhilipp Zabel 465017e5142SPhilipp Zabel /* 466339d7492SUwe Kleine-König * Set the critical trip point at 5 °C under max 467339d7492SUwe Kleine-König * Set the passive trip point at 10 °C under max (changeable via sysfs) 468017e5142SPhilipp Zabel */ 46930233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature = data->temp_max - (1000 * 10); 47030233a22SDaniel Lezcano trips[IMX_TRIP_CRITICAL].temperature = data->temp_max - (1000 * 5); 471ae621557SLeonard Crestez } 472ae621557SLeonard Crestez 473ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev) 474ae621557SLeonard Crestez { 475ae621557SLeonard Crestez struct regmap *map; 476ae621557SLeonard Crestez int ret; 477ae621557SLeonard Crestez u32 val; 478ae621557SLeonard Crestez 479ae621557SLeonard Crestez map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, 480ae621557SLeonard Crestez "fsl,tempmon-data"); 481ae621557SLeonard Crestez if (IS_ERR(map)) { 482ae621557SLeonard Crestez ret = PTR_ERR(map); 483ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); 484ae621557SLeonard Crestez return ret; 485ae621557SLeonard Crestez } 486ae621557SLeonard Crestez 487ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_ANA1, &val); 488ae621557SLeonard Crestez if (ret) { 489ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 490ae621557SLeonard Crestez return ret; 491ae621557SLeonard Crestez } 492ae621557SLeonard Crestez ret = imx_init_calib(pdev, val); 493ae621557SLeonard Crestez if (ret) 494ae621557SLeonard Crestez return ret; 495ae621557SLeonard Crestez 496ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_MEM0, &val); 497ae621557SLeonard Crestez if (ret) { 498ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 499ae621557SLeonard Crestez return ret; 500ae621557SLeonard Crestez } 501ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 502ae621557SLeonard Crestez 503ae621557SLeonard Crestez return 0; 504ae621557SLeonard Crestez } 505ae621557SLeonard Crestez 506ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev) 507ae621557SLeonard Crestez { 508ae621557SLeonard Crestez int ret; 509ae621557SLeonard Crestez u32 val; 510ae621557SLeonard Crestez 511ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val); 512ae621557SLeonard Crestez if (ret) 513ae621557SLeonard Crestez return ret; 514be926ceeSJean-Christophe Dubois 515be926ceeSJean-Christophe Dubois ret = imx_init_calib(pdev, val); 516be926ceeSJean-Christophe Dubois if (ret) 517be926ceeSJean-Christophe Dubois return ret; 518ae621557SLeonard Crestez 519ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val); 520ae621557SLeonard Crestez if (ret) 521ae621557SLeonard Crestez return ret; 522ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 523017e5142SPhilipp Zabel 524ca3de46bSShawn Guo return 0; 525ca3de46bSShawn Guo } 526ca3de46bSShawn Guo 52737713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev) 52837713a1eSPhilipp Zabel { 52937713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 53037713a1eSPhilipp Zabel 53137713a1eSPhilipp Zabel disable_irq_nosync(irq); 53237713a1eSPhilipp Zabel data->irq_enabled = false; 53337713a1eSPhilipp Zabel 53437713a1eSPhilipp Zabel return IRQ_WAKE_THREAD; 53537713a1eSPhilipp Zabel } 53637713a1eSPhilipp Zabel 53737713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) 53837713a1eSPhilipp Zabel { 53937713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 54037713a1eSPhilipp Zabel 541dec07d39SDaniel Lezcano dev_dbg(data->dev, "THERMAL ALARM: T > %d\n", data->alarm_temp / 1000); 54237713a1eSPhilipp Zabel 5430e70f466SSrinivas Pandruvada thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED); 54437713a1eSPhilipp Zabel 54537713a1eSPhilipp Zabel return IRQ_HANDLED; 54637713a1eSPhilipp Zabel } 54737713a1eSPhilipp Zabel 5483c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = { 5493c94f17eSAnson Huang { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, }, 5503c94f17eSAnson Huang { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, }, 551f085f672SAnson Huang { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, }, 5523c94f17eSAnson Huang { /* end */ } 5533c94f17eSAnson Huang }; 5543c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match); 5553c94f17eSAnson Huang 556c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ 557a1d00154SBastian Stender /* 558a1d00154SBastian Stender * Create cooling device in case no #cooling-cells property is available in 559a1d00154SBastian Stender * CPU node 560a1d00154SBastian Stender */ 561a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data) 562a1d00154SBastian Stender { 563c589c566SAnson Huang struct device_node *np; 564b45fd13bSAnson Huang int ret = 0; 565a1d00154SBastian Stender 566c589c566SAnson Huang data->policy = cpufreq_cpu_get(0); 567c589c566SAnson Huang if (!data->policy) { 568c589c566SAnson Huang pr_debug("%s: CPUFreq policy not found\n", __func__); 569c589c566SAnson Huang return -EPROBE_DEFER; 570c589c566SAnson Huang } 571c589c566SAnson Huang 572c589c566SAnson Huang np = of_get_cpu_node(data->policy->cpu, NULL); 573c589c566SAnson Huang 574*86df7d19SRob Herring if (!np || !of_property_present(np, "#cooling-cells")) { 575a1d00154SBastian Stender data->cdev = cpufreq_cooling_register(data->policy); 576a1d00154SBastian Stender if (IS_ERR(data->cdev)) { 577a1d00154SBastian Stender ret = PTR_ERR(data->cdev); 578a1d00154SBastian Stender cpufreq_cpu_put(data->policy); 579a1d00154SBastian Stender } 580a1d00154SBastian Stender } 581a1d00154SBastian Stender 582b45fd13bSAnson Huang of_node_put(np); 583b45fd13bSAnson Huang 584b45fd13bSAnson Huang return ret; 585a1d00154SBastian Stender } 586a1d00154SBastian Stender 587c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data) 588c589c566SAnson Huang { 589c589c566SAnson Huang cpufreq_cooling_unregister(data->cdev); 590c589c566SAnson Huang cpufreq_cpu_put(data->policy); 591c589c566SAnson Huang } 592c589c566SAnson Huang 593c589c566SAnson Huang #else 594c589c566SAnson Huang 595c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data) 596c589c566SAnson Huang { 597c589c566SAnson Huang return 0; 598c589c566SAnson Huang } 599c589c566SAnson Huang 600c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data) 601c589c566SAnson Huang { 602c589c566SAnson Huang } 603c589c566SAnson Huang #endif 604c589c566SAnson Huang 605ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev) 606ca3de46bSShawn Guo { 607ca3de46bSShawn Guo struct imx_thermal_data *data; 608ca3de46bSShawn Guo struct regmap *map; 60937713a1eSPhilipp Zabel int measure_freq; 610ca3de46bSShawn Guo int ret; 611ca3de46bSShawn Guo 612ca3de46bSShawn Guo data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 613ca3de46bSShawn Guo if (!data) 614ca3de46bSShawn Guo return -ENOMEM; 615ca3de46bSShawn Guo 6164cf2ddf1SOleksij Rempel data->dev = &pdev->dev; 6174cf2ddf1SOleksij Rempel 618ca3de46bSShawn Guo map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); 619ca3de46bSShawn Guo if (IS_ERR(map)) { 620ca3de46bSShawn Guo ret = PTR_ERR(map); 621ca3de46bSShawn Guo dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); 622ca3de46bSShawn Guo return ret; 623ca3de46bSShawn Guo } 624ca3de46bSShawn Guo data->tempmon = map; 625ca3de46bSShawn Guo 626829bc78aSCorentin LABBE data->socdata = of_device_get_match_data(&pdev->dev); 6278b051ec3SShailendra Verma if (!data->socdata) { 6288b051ec3SShailendra Verma dev_err(&pdev->dev, "no device match found\n"); 6298b051ec3SShailendra Verma return -ENODEV; 6308b051ec3SShailendra Verma } 6313c94f17eSAnson Huang 6323c94f17eSAnson Huang /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */ 6333c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) { 634f085f672SAnson Huang regmap_write(map, IMX6_MISC1 + REG_CLR, 635f085f672SAnson Huang IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW 636f085f672SAnson Huang | IMX6_MISC1_IRQ_TEMPPANIC); 6373c94f17eSAnson Huang /* 6383c94f17eSAnson Huang * reset value of LOW ALARM is incorrect, set it to lowest 6393c94f17eSAnson Huang * value to avoid false trigger of low alarm. 6403c94f17eSAnson Huang */ 641f085f672SAnson Huang regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET, 642f085f672SAnson Huang data->socdata->low_alarm_mask); 6433c94f17eSAnson Huang } 6443c94f17eSAnson Huang 64537713a1eSPhilipp Zabel data->irq = platform_get_irq(pdev, 0); 64637713a1eSPhilipp Zabel if (data->irq < 0) 64737713a1eSPhilipp Zabel return data->irq; 64837713a1eSPhilipp Zabel 649ca3de46bSShawn Guo platform_set_drvdata(pdev, data); 650ca3de46bSShawn Guo 651*86df7d19SRob Herring if (of_property_present(pdev->dev.of_node, "nvmem-cells")) { 652ae621557SLeonard Crestez ret = imx_init_from_nvmem_cells(pdev); 6535f3c0200SAnson Huang if (ret) 6545f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret, 6555f3c0200SAnson Huang "failed to init from nvmem\n"); 656ae621557SLeonard Crestez } else { 657ae621557SLeonard Crestez ret = imx_init_from_tempmon_data(pdev); 658ae621557SLeonard Crestez if (ret) { 659337a4aecSAnson Huang dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n"); 660ae621557SLeonard Crestez return ret; 661ae621557SLeonard Crestez } 662ca3de46bSShawn Guo } 663ca3de46bSShawn Guo 664ca3de46bSShawn Guo /* Make sure sensor is in known good state for measurements */ 665f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 666f085f672SAnson Huang data->socdata->power_down_mask); 667f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 668f085f672SAnson Huang data->socdata->measure_temp_mask); 669f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, 670f085f672SAnson Huang data->socdata->measure_freq_mask); 671f085f672SAnson Huang if (data->socdata->version != TEMPMON_IMX7D) 672f085f672SAnson Huang regmap_write(map, IMX6_MISC0 + REG_SET, 673f085f672SAnson Huang IMX6_MISC0_REFTOP_SELBIASOFF); 674f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 675f085f672SAnson Huang data->socdata->power_down_mask); 676ca3de46bSShawn Guo 677a1d00154SBastian Stender ret = imx_thermal_register_legacy_cooling(data); 6785f3c0200SAnson Huang if (ret) 6795f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret, 6805f3c0200SAnson Huang "failed to register cpufreq cooling device\n"); 681ca3de46bSShawn Guo 68290a21ff5SHeiner Kallweit data->thermal_clk = devm_clk_get(&pdev->dev, NULL); 68390a21ff5SHeiner Kallweit if (IS_ERR(data->thermal_clk)) { 68490a21ff5SHeiner Kallweit ret = PTR_ERR(data->thermal_clk); 68590a21ff5SHeiner Kallweit if (ret != -EPROBE_DEFER) 68690a21ff5SHeiner Kallweit dev_err(&pdev->dev, 68790a21ff5SHeiner Kallweit "failed to get thermal clk: %d\n", ret); 688c589c566SAnson Huang goto legacy_cleanup; 68990a21ff5SHeiner Kallweit } 69090a21ff5SHeiner Kallweit 69190a21ff5SHeiner Kallweit /* 69290a21ff5SHeiner Kallweit * Thermal sensor needs clk on to get correct value, normally 69390a21ff5SHeiner Kallweit * we should enable its clk before taking measurement and disable 69490a21ff5SHeiner Kallweit * clk after measurement is done, but if alarm function is enabled, 69590a21ff5SHeiner Kallweit * hardware will auto measure the temperature periodically, so we 69690a21ff5SHeiner Kallweit * need to keep the clk always on for alarm function. 69790a21ff5SHeiner Kallweit */ 69890a21ff5SHeiner Kallweit ret = clk_prepare_enable(data->thermal_clk); 69990a21ff5SHeiner Kallweit if (ret) { 70090a21ff5SHeiner Kallweit dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret); 701c589c566SAnson Huang goto legacy_cleanup; 70290a21ff5SHeiner Kallweit } 70390a21ff5SHeiner Kallweit 70430233a22SDaniel Lezcano data->tz = thermal_zone_device_register_with_trips("imx_thermal_zone", 70530233a22SDaniel Lezcano trips, 70630233a22SDaniel Lezcano ARRAY_SIZE(trips), 707017e5142SPhilipp Zabel BIT(IMX_TRIP_PASSIVE), data, 708ca3de46bSShawn Guo &imx_tz_ops, NULL, 709ca3de46bSShawn Guo IMX_PASSIVE_DELAY, 710ca3de46bSShawn Guo IMX_POLLING_DELAY); 711ca3de46bSShawn Guo if (IS_ERR(data->tz)) { 712ca3de46bSShawn Guo ret = PTR_ERR(data->tz); 713ca3de46bSShawn Guo dev_err(&pdev->dev, 714ca3de46bSShawn Guo "failed to register thermal zone device %d\n", ret); 715b6ad3981SAnson Huang goto clk_disable; 716ca3de46bSShawn Guo } 717ca3de46bSShawn Guo 718a2291badSTim Harvey dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC" 719a2291badSTim Harvey " critical:%dC passive:%dC\n", data->temp_grade, 72030233a22SDaniel Lezcano data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000, 72130233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature / 1000); 722a2291badSTim Harvey 72337713a1eSPhilipp Zabel /* Enable measurements at ~ 10 Hz */ 724f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, 725f085f672SAnson Huang data->socdata->measure_freq_mask); 72637713a1eSPhilipp Zabel measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ 727f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET, 728f085f672SAnson Huang measure_freq << data->socdata->measure_freq_shift); 72930233a22SDaniel Lezcano imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature); 7303c94f17eSAnson Huang 7313c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) 73230233a22SDaniel Lezcano imx_set_panic_temp(data, trips[IMX_TRIP_CRITICAL].temperature); 7333c94f17eSAnson Huang 734f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 735f085f672SAnson Huang data->socdata->power_down_mask); 736f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 737f085f672SAnson Huang data->socdata->measure_temp_mask); 7384cf2ddf1SOleksij Rempel /* After power up, we need a delay before first access can be done. */ 7394cf2ddf1SOleksij Rempel usleep_range(20, 50); 7404cf2ddf1SOleksij Rempel 7414cf2ddf1SOleksij Rempel /* the core was configured and enabled just before */ 7424cf2ddf1SOleksij Rempel pm_runtime_set_active(&pdev->dev); 7434cf2ddf1SOleksij Rempel pm_runtime_enable(data->dev); 7444cf2ddf1SOleksij Rempel 7454cf2ddf1SOleksij Rempel ret = pm_runtime_resume_and_get(data->dev); 7464cf2ddf1SOleksij Rempel if (ret < 0) 7474cf2ddf1SOleksij Rempel goto disable_runtime_pm; 74837713a1eSPhilipp Zabel 749cf1ba1d7SMikhail Lappo data->irq_enabled = true; 7507f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_enable(data->tz); 7517f4957beSAndrzej Pietrasiewicz if (ret) 7527f4957beSAndrzej Pietrasiewicz goto thermal_zone_unregister; 753cf1ba1d7SMikhail Lappo 75484866ee5SBai Ping ret = devm_request_threaded_irq(&pdev->dev, data->irq, 75584866ee5SBai Ping imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, 75684866ee5SBai Ping 0, "imx_thermal", data); 75784866ee5SBai Ping if (ret < 0) { 75884866ee5SBai Ping dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); 759b6ad3981SAnson Huang goto thermal_zone_unregister; 76084866ee5SBai Ping } 76184866ee5SBai Ping 7624cf2ddf1SOleksij Rempel pm_runtime_put(data->dev); 7634cf2ddf1SOleksij Rempel 764ca3de46bSShawn Guo return 0; 765b6ad3981SAnson Huang 766b6ad3981SAnson Huang thermal_zone_unregister: 767b6ad3981SAnson Huang thermal_zone_device_unregister(data->tz); 7684cf2ddf1SOleksij Rempel disable_runtime_pm: 7694cf2ddf1SOleksij Rempel pm_runtime_put_noidle(data->dev); 7704cf2ddf1SOleksij Rempel pm_runtime_disable(data->dev); 771b6ad3981SAnson Huang clk_disable: 772b6ad3981SAnson Huang clk_disable_unprepare(data->thermal_clk); 773c589c566SAnson Huang legacy_cleanup: 774c589c566SAnson Huang imx_thermal_unregister_legacy_cooling(data); 775b6ad3981SAnson Huang 776b6ad3981SAnson Huang return ret; 777ca3de46bSShawn Guo } 778ca3de46bSShawn Guo 779ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev) 780ca3de46bSShawn Guo { 781ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 78237713a1eSPhilipp Zabel 7834cf2ddf1SOleksij Rempel pm_runtime_put_noidle(data->dev); 7844cf2ddf1SOleksij Rempel pm_runtime_disable(data->dev); 785ca3de46bSShawn Guo 786ca3de46bSShawn Guo thermal_zone_device_unregister(data->tz); 7879db11010SAnson Huang imx_thermal_unregister_legacy_cooling(data); 788ca3de46bSShawn Guo 789ca3de46bSShawn Guo return 0; 790ca3de46bSShawn Guo } 791ca3de46bSShawn Guo 792b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev) 793ca3de46bSShawn Guo { 794ca3de46bSShawn Guo struct imx_thermal_data *data = dev_get_drvdata(dev); 7957f4957beSAndrzej Pietrasiewicz int ret; 796ca3de46bSShawn Guo 797ca3de46bSShawn Guo /* 798b46cce59SAnson Huang * Need to disable thermal sensor, otherwise, when thermal core 799b46cce59SAnson Huang * try to get temperature before thermal sensor resume, a wrong 800b46cce59SAnson Huang * temperature will be read as the thermal sensor is powered 801f5e50bf4SAndrzej Pietrasiewicz * down. This is done in change_mode() operation called from 8027f4957beSAndrzej Pietrasiewicz * thermal_zone_device_disable() 803ca3de46bSShawn Guo */ 8047f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_disable(data->tz); 8057f4957beSAndrzej Pietrasiewicz if (ret) 8067f4957beSAndrzej Pietrasiewicz return ret; 807ca3de46bSShawn Guo 8084cf2ddf1SOleksij Rempel return pm_runtime_force_suspend(data->dev); 809ca3de46bSShawn Guo } 810ca3de46bSShawn Guo 811b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev) 812ca3de46bSShawn Guo { 813b46cce59SAnson Huang struct imx_thermal_data *data = dev_get_drvdata(dev); 814e3bdc8d7SArvind Yadav int ret; 815b46cce59SAnson Huang 8164cf2ddf1SOleksij Rempel ret = pm_runtime_force_resume(data->dev); 817e3bdc8d7SArvind Yadav if (ret) 818e3bdc8d7SArvind Yadav return ret; 819b46cce59SAnson Huang /* Enabled thermal sensor after resume */ 8204cf2ddf1SOleksij Rempel return thermal_zone_device_enable(data->tz); 8214cf2ddf1SOleksij Rempel } 8224cf2ddf1SOleksij Rempel 8234cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev) 8244cf2ddf1SOleksij Rempel { 8254cf2ddf1SOleksij Rempel struct imx_thermal_data *data = dev_get_drvdata(dev); 8264cf2ddf1SOleksij Rempel const struct thermal_soc_data *socdata = data->socdata; 8274cf2ddf1SOleksij Rempel struct regmap *map = data->tempmon; 8284cf2ddf1SOleksij Rempel int ret; 8294cf2ddf1SOleksij Rempel 8304cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR, 8314cf2ddf1SOleksij Rempel socdata->measure_temp_mask); 8327f4957beSAndrzej Pietrasiewicz if (ret) 8337f4957beSAndrzej Pietrasiewicz return ret; 834b46cce59SAnson Huang 8354cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_SET, 8364cf2ddf1SOleksij Rempel socdata->power_down_mask); 8374cf2ddf1SOleksij Rempel if (ret) 8384cf2ddf1SOleksij Rempel return ret; 8394cf2ddf1SOleksij Rempel 8404cf2ddf1SOleksij Rempel clk_disable_unprepare(data->thermal_clk); 8414cf2ddf1SOleksij Rempel 842ca3de46bSShawn Guo return 0; 843ca3de46bSShawn Guo } 844ca3de46bSShawn Guo 8454cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_resume(struct device *dev) 8464cf2ddf1SOleksij Rempel { 8474cf2ddf1SOleksij Rempel struct imx_thermal_data *data = dev_get_drvdata(dev); 8484cf2ddf1SOleksij Rempel const struct thermal_soc_data *socdata = data->socdata; 8494cf2ddf1SOleksij Rempel struct regmap *map = data->tempmon; 8504cf2ddf1SOleksij Rempel int ret; 8514cf2ddf1SOleksij Rempel 8524cf2ddf1SOleksij Rempel ret = clk_prepare_enable(data->thermal_clk); 8534cf2ddf1SOleksij Rempel if (ret) 8544cf2ddf1SOleksij Rempel return ret; 8554cf2ddf1SOleksij Rempel 8564cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR, 8574cf2ddf1SOleksij Rempel socdata->power_down_mask); 8584cf2ddf1SOleksij Rempel if (ret) 8594cf2ddf1SOleksij Rempel return ret; 8604cf2ddf1SOleksij Rempel 8614cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_SET, 8624cf2ddf1SOleksij Rempel socdata->measure_temp_mask); 8634cf2ddf1SOleksij Rempel if (ret) 8644cf2ddf1SOleksij Rempel return ret; 8654cf2ddf1SOleksij Rempel 8664cf2ddf1SOleksij Rempel /* 8674cf2ddf1SOleksij Rempel * According to the temp sensor designers, it may require up to ~17us 8684cf2ddf1SOleksij Rempel * to complete a measurement. 8694cf2ddf1SOleksij Rempel */ 8704cf2ddf1SOleksij Rempel usleep_range(20, 50); 8714cf2ddf1SOleksij Rempel 8724cf2ddf1SOleksij Rempel return 0; 8734cf2ddf1SOleksij Rempel } 8744cf2ddf1SOleksij Rempel 8754cf2ddf1SOleksij Rempel static const struct dev_pm_ops imx_thermal_pm_ops = { 8764cf2ddf1SOleksij Rempel SET_SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume) 8774cf2ddf1SOleksij Rempel SET_RUNTIME_PM_OPS(imx_thermal_runtime_suspend, 8784cf2ddf1SOleksij Rempel imx_thermal_runtime_resume, NULL) 8794cf2ddf1SOleksij Rempel }; 880ca3de46bSShawn Guo 881ca3de46bSShawn Guo static struct platform_driver imx_thermal = { 882ca3de46bSShawn Guo .driver = { 883ca3de46bSShawn Guo .name = "imx_thermal", 884ca3de46bSShawn Guo .pm = &imx_thermal_pm_ops, 885ca3de46bSShawn Guo .of_match_table = of_imx_thermal_match, 886ca3de46bSShawn Guo }, 887ca3de46bSShawn Guo .probe = imx_thermal_probe, 888ca3de46bSShawn Guo .remove = imx_thermal_remove, 889ca3de46bSShawn Guo }; 890ca3de46bSShawn Guo module_platform_driver(imx_thermal); 891ca3de46bSShawn Guo 892ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc."); 893ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); 894ca3de46bSShawn Guo MODULE_LICENSE("GPL v2"); 895ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal"); 896