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 { 255*5f68d078SDaniel 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 268f085f672SAnson Huang if ((val & soc_data->temp_valid_mask) == 0) { 269ca3de46bSShawn Guo dev_dbg(&tz->device, "temp measurement never finished\n"); 270ca3de46bSShawn Guo return -EAGAIN; 271ca3de46bSShawn Guo } 272ca3de46bSShawn Guo 273f085f672SAnson Huang n_meas = (val & soc_data->temp_value_mask) 274f085f672SAnson Huang >> soc_data->temp_value_shift; 275ca3de46bSShawn Guo 276ae621557SLeonard Crestez /* See imx_init_calib() for formula derivation */ 277f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) 278f085f672SAnson Huang *temp = (n_meas - data->c1 + 25) * 1000; 279f085f672SAnson Huang else 280749e8be7SAnson Huang *temp = data->c2 - n_meas * data->c1; 281ca3de46bSShawn Guo 2823c94f17eSAnson Huang /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */ 2833c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6Q) { 28430233a22SDaniel Lezcano if (data->alarm_temp == trips[IMX_TRIP_PASSIVE].temperature && 28530233a22SDaniel Lezcano *temp >= trips[IMX_TRIP_PASSIVE].temperature) 28630233a22SDaniel Lezcano imx_set_alarm_temp(data, trips[IMX_TRIP_CRITICAL].temperature); 28730233a22SDaniel Lezcano if (data->alarm_temp == trips[IMX_TRIP_CRITICAL].temperature && 28830233a22SDaniel Lezcano *temp < trips[IMX_TRIP_PASSIVE].temperature) { 28930233a22SDaniel Lezcano imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature); 29017e8351aSSascha Hauer dev_dbg(&tz->device, "thermal alarm off: T < %d\n", 29137713a1eSPhilipp Zabel data->alarm_temp / 1000); 29237713a1eSPhilipp Zabel } 2933c94f17eSAnson Huang } 29437713a1eSPhilipp Zabel 29537713a1eSPhilipp Zabel if (*temp != data->last_temp) { 29617e8351aSSascha Hauer dev_dbg(&tz->device, "millicelsius: %d\n", *temp); 29737713a1eSPhilipp Zabel data->last_temp = *temp; 29837713a1eSPhilipp Zabel } 29937713a1eSPhilipp Zabel 30037713a1eSPhilipp Zabel /* Reenable alarm IRQ if temperature below alarm temperature */ 30137713a1eSPhilipp Zabel if (!data->irq_enabled && *temp < data->alarm_temp) { 30237713a1eSPhilipp Zabel data->irq_enabled = true; 30337713a1eSPhilipp Zabel enable_irq(data->irq); 304ca3de46bSShawn Guo } 305ca3de46bSShawn Guo 3064cf2ddf1SOleksij Rempel pm_runtime_put(data->dev); 3074cf2ddf1SOleksij Rempel 308ca3de46bSShawn Guo return 0; 309ca3de46bSShawn Guo } 310ca3de46bSShawn Guo 311f5e50bf4SAndrzej Pietrasiewicz static int imx_change_mode(struct thermal_zone_device *tz, 312ca3de46bSShawn Guo enum thermal_device_mode mode) 313ca3de46bSShawn Guo { 314*5f68d078SDaniel Lezcano struct imx_thermal_data *data = thermal_zone_device_priv(tz); 315ca3de46bSShawn Guo 316ca3de46bSShawn Guo if (mode == THERMAL_DEVICE_ENABLED) { 3174cf2ddf1SOleksij Rempel pm_runtime_get(data->dev); 31837713a1eSPhilipp Zabel 31937713a1eSPhilipp Zabel if (!data->irq_enabled) { 32037713a1eSPhilipp Zabel data->irq_enabled = true; 32137713a1eSPhilipp Zabel enable_irq(data->irq); 32237713a1eSPhilipp Zabel } 323ca3de46bSShawn Guo } else { 3244cf2ddf1SOleksij Rempel pm_runtime_put(data->dev); 32537713a1eSPhilipp Zabel 32637713a1eSPhilipp Zabel if (data->irq_enabled) { 32737713a1eSPhilipp Zabel disable_irq(data->irq); 32837713a1eSPhilipp Zabel data->irq_enabled = false; 32937713a1eSPhilipp Zabel } 330ca3de46bSShawn Guo } 331ca3de46bSShawn Guo 332ca3de46bSShawn Guo return 0; 333ca3de46bSShawn Guo } 334ca3de46bSShawn Guo 33517e8351aSSascha Hauer static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp) 336ca3de46bSShawn Guo { 33730233a22SDaniel Lezcano *temp = trips[IMX_TRIP_CRITICAL].temperature; 338017e5142SPhilipp Zabel 339017e5142SPhilipp Zabel return 0; 340017e5142SPhilipp Zabel } 341017e5142SPhilipp Zabel 342017e5142SPhilipp Zabel static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, 34317e8351aSSascha Hauer int temp) 344017e5142SPhilipp Zabel { 345*5f68d078SDaniel Lezcano struct imx_thermal_data *data = thermal_zone_device_priv(tz); 3464cf2ddf1SOleksij Rempel int ret; 3474cf2ddf1SOleksij Rempel 3484cf2ddf1SOleksij Rempel ret = pm_runtime_resume_and_get(data->dev); 3494cf2ddf1SOleksij Rempel if (ret < 0) 3504cf2ddf1SOleksij Rempel return ret; 351017e5142SPhilipp Zabel 352a2291badSTim Harvey /* do not allow changing critical threshold */ 353017e5142SPhilipp Zabel if (trip == IMX_TRIP_CRITICAL) 354017e5142SPhilipp Zabel return -EPERM; 355017e5142SPhilipp Zabel 356a2291badSTim Harvey /* do not allow passive to be set higher than critical */ 35730233a22SDaniel Lezcano if (temp < 0 || temp > trips[IMX_TRIP_CRITICAL].temperature) 358017e5142SPhilipp Zabel return -EINVAL; 359017e5142SPhilipp Zabel 36030233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature = temp; 361017e5142SPhilipp Zabel 36237713a1eSPhilipp Zabel imx_set_alarm_temp(data, temp); 36337713a1eSPhilipp Zabel 3644cf2ddf1SOleksij Rempel pm_runtime_put(data->dev); 3654cf2ddf1SOleksij Rempel 366ca3de46bSShawn Guo return 0; 367ca3de46bSShawn Guo } 368ca3de46bSShawn Guo 369ca3de46bSShawn Guo static int imx_bind(struct thermal_zone_device *tz, 370ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 371ca3de46bSShawn Guo { 372ca3de46bSShawn Guo int ret; 373ca3de46bSShawn Guo 374ca3de46bSShawn Guo ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, 375ca3de46bSShawn Guo THERMAL_NO_LIMIT, 3766cd9e9f6SKapileshwar Singh THERMAL_NO_LIMIT, 3776cd9e9f6SKapileshwar Singh THERMAL_WEIGHT_DEFAULT); 378ca3de46bSShawn Guo if (ret) { 379ca3de46bSShawn Guo dev_err(&tz->device, 380ca3de46bSShawn Guo "binding zone %s with cdev %s failed:%d\n", 381ca3de46bSShawn Guo tz->type, cdev->type, ret); 382ca3de46bSShawn Guo return ret; 383ca3de46bSShawn Guo } 384ca3de46bSShawn Guo 385ca3de46bSShawn Guo return 0; 386ca3de46bSShawn Guo } 387ca3de46bSShawn Guo 388ca3de46bSShawn Guo static int imx_unbind(struct thermal_zone_device *tz, 389ca3de46bSShawn Guo struct thermal_cooling_device *cdev) 390ca3de46bSShawn Guo { 391ca3de46bSShawn Guo int ret; 392ca3de46bSShawn Guo 393ca3de46bSShawn Guo ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); 394ca3de46bSShawn Guo if (ret) { 395ca3de46bSShawn Guo dev_err(&tz->device, 396ca3de46bSShawn Guo "unbinding zone %s with cdev %s failed:%d\n", 397ca3de46bSShawn Guo tz->type, cdev->type, ret); 398ca3de46bSShawn Guo return ret; 399ca3de46bSShawn Guo } 400ca3de46bSShawn Guo 401ca3de46bSShawn Guo return 0; 402ca3de46bSShawn Guo } 403ca3de46bSShawn Guo 404cbb07bb3SEduardo Valentin static struct thermal_zone_device_ops imx_tz_ops = { 405ca3de46bSShawn Guo .bind = imx_bind, 406ca3de46bSShawn Guo .unbind = imx_unbind, 407ca3de46bSShawn Guo .get_temp = imx_get_temp, 408f5e50bf4SAndrzej Pietrasiewicz .change_mode = imx_change_mode, 409ca3de46bSShawn Guo .get_crit_temp = imx_get_crit_temp, 410017e5142SPhilipp Zabel .set_trip_temp = imx_set_trip_temp, 411ca3de46bSShawn Guo }; 412ca3de46bSShawn Guo 413e4bb2240SUwe Kleine-König static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1) 414ca3de46bSShawn Guo { 415ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 4164e5f61caSUwe Kleine-König int n1; 417749e8be7SAnson Huang u64 temp64; 418ca3de46bSShawn Guo 419e4bb2240SUwe Kleine-König if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) { 420ca3de46bSShawn Guo dev_err(&pdev->dev, "invalid sensor calibration data\n"); 421ca3de46bSShawn Guo return -EINVAL; 422ca3de46bSShawn Guo } 423ca3de46bSShawn Guo 424ca3de46bSShawn Guo /* 425f085f672SAnson Huang * On i.MX7D, we only use the calibration data at 25C to get the temp, 426f085f672SAnson Huang * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C. 427f085f672SAnson Huang */ 428f085f672SAnson Huang if (data->socdata->version == TEMPMON_IMX7D) { 429f085f672SAnson Huang data->c1 = (ocotp_ana1 >> 9) & 0x1ff; 430f085f672SAnson Huang return 0; 431f085f672SAnson Huang } 432f085f672SAnson Huang 433f085f672SAnson Huang /* 434c5bbdb4bSUwe Kleine-König * The sensor is calibrated at 25 °C (aka T1) and the value measured 435c5bbdb4bSUwe Kleine-König * (aka N1) at this temperature is provided in bits [31:20] in the 436c5bbdb4bSUwe Kleine-König * i.MX's OCOTP value ANA1. 437c5bbdb4bSUwe Kleine-König * To find the actual temperature T, the following formula has to be used 438c5bbdb4bSUwe Kleine-König * when reading value n from the sensor: 439c5bbdb4bSUwe Kleine-König * 4404e5f61caSUwe Kleine-König * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C 4414e5f61caSUwe Kleine-König * = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C 4424e5f61caSUwe Kleine-König * = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C 443c5bbdb4bSUwe Kleine-König * = c2 - c1 * N 444c5bbdb4bSUwe Kleine-König * 445c5bbdb4bSUwe Kleine-König * with 446c5bbdb4bSUwe Kleine-König * 4474e5f61caSUwe Kleine-König * T1' = 28.580661 °C 4484e5f61caSUwe Kleine-König * c1 = 1 / (0.0015423 * N1 - 0.4297157) °C 4494e5f61caSUwe Kleine-König * c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C 4504e5f61caSUwe Kleine-König * = T1' + N1 * c1 451ca3de46bSShawn Guo */ 452e4bb2240SUwe Kleine-König n1 = ocotp_ana1 >> 20; 453ca3de46bSShawn Guo 4544e5f61caSUwe Kleine-König temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */ 455c5bbdb4bSUwe Kleine-König temp64 *= 1000; /* to get result in °mC */ 4564e5f61caSUwe Kleine-König do_div(temp64, 15423 * n1 - 4148468); 457749e8be7SAnson Huang data->c1 = temp64; 4584e5f61caSUwe Kleine-König data->c2 = n1 * data->c1 + 28581; 459ca3de46bSShawn Guo 460ae621557SLeonard Crestez return 0; 461a2291badSTim Harvey } 462a2291badSTim Harvey 463e4bb2240SUwe Kleine-König static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0) 464ae621557SLeonard Crestez { 465ae621557SLeonard Crestez struct imx_thermal_data *data = platform_get_drvdata(pdev); 466ae621557SLeonard Crestez 467a2291badSTim Harvey /* The maximum die temp is specified by the Temperature Grade */ 468e4bb2240SUwe Kleine-König switch ((ocotp_mem0 >> 6) & 0x3) { 469339d7492SUwe Kleine-König case 0: /* Commercial (0 to 95 °C) */ 470a2291badSTim Harvey data->temp_grade = "Commercial"; 471a2291badSTim Harvey data->temp_max = 95000; 472a2291badSTim Harvey break; 473339d7492SUwe Kleine-König case 1: /* Extended Commercial (-20 °C to 105 °C) */ 474a2291badSTim Harvey data->temp_grade = "Extended Commercial"; 475a2291badSTim Harvey data->temp_max = 105000; 476a2291badSTim Harvey break; 477339d7492SUwe Kleine-König case 2: /* Industrial (-40 °C to 105 °C) */ 478a2291badSTim Harvey data->temp_grade = "Industrial"; 479a2291badSTim Harvey data->temp_max = 105000; 480a2291badSTim Harvey break; 481339d7492SUwe Kleine-König case 3: /* Automotive (-40 °C to 125 °C) */ 482a2291badSTim Harvey data->temp_grade = "Automotive"; 483a2291badSTim Harvey data->temp_max = 125000; 484a2291badSTim Harvey break; 485a2291badSTim Harvey } 486017e5142SPhilipp Zabel 487017e5142SPhilipp Zabel /* 488339d7492SUwe Kleine-König * Set the critical trip point at 5 °C under max 489339d7492SUwe Kleine-König * Set the passive trip point at 10 °C under max (changeable via sysfs) 490017e5142SPhilipp Zabel */ 49130233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature = data->temp_max - (1000 * 10); 49230233a22SDaniel Lezcano trips[IMX_TRIP_CRITICAL].temperature = data->temp_max - (1000 * 5); 493ae621557SLeonard Crestez } 494ae621557SLeonard Crestez 495ae621557SLeonard Crestez static int imx_init_from_tempmon_data(struct platform_device *pdev) 496ae621557SLeonard Crestez { 497ae621557SLeonard Crestez struct regmap *map; 498ae621557SLeonard Crestez int ret; 499ae621557SLeonard Crestez u32 val; 500ae621557SLeonard Crestez 501ae621557SLeonard Crestez map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, 502ae621557SLeonard Crestez "fsl,tempmon-data"); 503ae621557SLeonard Crestez if (IS_ERR(map)) { 504ae621557SLeonard Crestez ret = PTR_ERR(map); 505ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); 506ae621557SLeonard Crestez return ret; 507ae621557SLeonard Crestez } 508ae621557SLeonard Crestez 509ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_ANA1, &val); 510ae621557SLeonard Crestez if (ret) { 511ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 512ae621557SLeonard Crestez return ret; 513ae621557SLeonard Crestez } 514ae621557SLeonard Crestez ret = imx_init_calib(pdev, val); 515ae621557SLeonard Crestez if (ret) 516ae621557SLeonard Crestez return ret; 517ae621557SLeonard Crestez 518ae621557SLeonard Crestez ret = regmap_read(map, OCOTP_MEM0, &val); 519ae621557SLeonard Crestez if (ret) { 520ae621557SLeonard Crestez dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); 521ae621557SLeonard Crestez return ret; 522ae621557SLeonard Crestez } 523ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 524ae621557SLeonard Crestez 525ae621557SLeonard Crestez return 0; 526ae621557SLeonard Crestez } 527ae621557SLeonard Crestez 528ae621557SLeonard Crestez static int imx_init_from_nvmem_cells(struct platform_device *pdev) 529ae621557SLeonard Crestez { 530ae621557SLeonard Crestez int ret; 531ae621557SLeonard Crestez u32 val; 532ae621557SLeonard Crestez 533ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val); 534ae621557SLeonard Crestez if (ret) 535ae621557SLeonard Crestez return ret; 536be926ceeSJean-Christophe Dubois 537be926ceeSJean-Christophe Dubois ret = imx_init_calib(pdev, val); 538be926ceeSJean-Christophe Dubois if (ret) 539be926ceeSJean-Christophe Dubois return ret; 540ae621557SLeonard Crestez 541ae621557SLeonard Crestez ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val); 542ae621557SLeonard Crestez if (ret) 543ae621557SLeonard Crestez return ret; 544ae621557SLeonard Crestez imx_init_temp_grade(pdev, val); 545017e5142SPhilipp Zabel 546ca3de46bSShawn Guo return 0; 547ca3de46bSShawn Guo } 548ca3de46bSShawn Guo 54937713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev) 55037713a1eSPhilipp Zabel { 55137713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 55237713a1eSPhilipp Zabel 55337713a1eSPhilipp Zabel disable_irq_nosync(irq); 55437713a1eSPhilipp Zabel data->irq_enabled = false; 55537713a1eSPhilipp Zabel 55637713a1eSPhilipp Zabel return IRQ_WAKE_THREAD; 55737713a1eSPhilipp Zabel } 55837713a1eSPhilipp Zabel 55937713a1eSPhilipp Zabel static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) 56037713a1eSPhilipp Zabel { 56137713a1eSPhilipp Zabel struct imx_thermal_data *data = dev; 56237713a1eSPhilipp Zabel 56317e8351aSSascha Hauer dev_dbg(&data->tz->device, "THERMAL ALARM: T > %d\n", 56437713a1eSPhilipp Zabel data->alarm_temp / 1000); 56537713a1eSPhilipp Zabel 5660e70f466SSrinivas Pandruvada thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED); 56737713a1eSPhilipp Zabel 56837713a1eSPhilipp Zabel return IRQ_HANDLED; 56937713a1eSPhilipp Zabel } 57037713a1eSPhilipp Zabel 5713c94f17eSAnson Huang static const struct of_device_id of_imx_thermal_match[] = { 5723c94f17eSAnson Huang { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, }, 5733c94f17eSAnson Huang { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, }, 574f085f672SAnson Huang { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, }, 5753c94f17eSAnson Huang { /* end */ } 5763c94f17eSAnson Huang }; 5773c94f17eSAnson Huang MODULE_DEVICE_TABLE(of, of_imx_thermal_match); 5783c94f17eSAnson Huang 579c589c566SAnson Huang #ifdef CONFIG_CPU_FREQ 580a1d00154SBastian Stender /* 581a1d00154SBastian Stender * Create cooling device in case no #cooling-cells property is available in 582a1d00154SBastian Stender * CPU node 583a1d00154SBastian Stender */ 584a1d00154SBastian Stender static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data) 585a1d00154SBastian Stender { 586c589c566SAnson Huang struct device_node *np; 587b45fd13bSAnson Huang int ret = 0; 588a1d00154SBastian Stender 589c589c566SAnson Huang data->policy = cpufreq_cpu_get(0); 590c589c566SAnson Huang if (!data->policy) { 591c589c566SAnson Huang pr_debug("%s: CPUFreq policy not found\n", __func__); 592c589c566SAnson Huang return -EPROBE_DEFER; 593c589c566SAnson Huang } 594c589c566SAnson Huang 595c589c566SAnson Huang np = of_get_cpu_node(data->policy->cpu, NULL); 596c589c566SAnson Huang 597a1d00154SBastian Stender if (!np || !of_find_property(np, "#cooling-cells", NULL)) { 598a1d00154SBastian Stender data->cdev = cpufreq_cooling_register(data->policy); 599a1d00154SBastian Stender if (IS_ERR(data->cdev)) { 600a1d00154SBastian Stender ret = PTR_ERR(data->cdev); 601a1d00154SBastian Stender cpufreq_cpu_put(data->policy); 602a1d00154SBastian Stender } 603a1d00154SBastian Stender } 604a1d00154SBastian Stender 605b45fd13bSAnson Huang of_node_put(np); 606b45fd13bSAnson Huang 607b45fd13bSAnson Huang return ret; 608a1d00154SBastian Stender } 609a1d00154SBastian Stender 610c589c566SAnson Huang static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data) 611c589c566SAnson Huang { 612c589c566SAnson Huang cpufreq_cooling_unregister(data->cdev); 613c589c566SAnson Huang cpufreq_cpu_put(data->policy); 614c589c566SAnson Huang } 615c589c566SAnson Huang 616c589c566SAnson Huang #else 617c589c566SAnson Huang 618c589c566SAnson Huang static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data) 619c589c566SAnson Huang { 620c589c566SAnson Huang return 0; 621c589c566SAnson Huang } 622c589c566SAnson Huang 623c589c566SAnson Huang static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data) 624c589c566SAnson Huang { 625c589c566SAnson Huang } 626c589c566SAnson Huang #endif 627c589c566SAnson Huang 628ca3de46bSShawn Guo static int imx_thermal_probe(struct platform_device *pdev) 629ca3de46bSShawn Guo { 630ca3de46bSShawn Guo struct imx_thermal_data *data; 631ca3de46bSShawn Guo struct regmap *map; 63237713a1eSPhilipp Zabel int measure_freq; 633ca3de46bSShawn Guo int ret; 634ca3de46bSShawn Guo 635ca3de46bSShawn Guo data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 636ca3de46bSShawn Guo if (!data) 637ca3de46bSShawn Guo return -ENOMEM; 638ca3de46bSShawn Guo 6394cf2ddf1SOleksij Rempel data->dev = &pdev->dev; 6404cf2ddf1SOleksij Rempel 641ca3de46bSShawn Guo map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); 642ca3de46bSShawn Guo if (IS_ERR(map)) { 643ca3de46bSShawn Guo ret = PTR_ERR(map); 644ca3de46bSShawn Guo dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); 645ca3de46bSShawn Guo return ret; 646ca3de46bSShawn Guo } 647ca3de46bSShawn Guo data->tempmon = map; 648ca3de46bSShawn Guo 649829bc78aSCorentin LABBE data->socdata = of_device_get_match_data(&pdev->dev); 6508b051ec3SShailendra Verma if (!data->socdata) { 6518b051ec3SShailendra Verma dev_err(&pdev->dev, "no device match found\n"); 6528b051ec3SShailendra Verma return -ENODEV; 6538b051ec3SShailendra Verma } 6543c94f17eSAnson Huang 6553c94f17eSAnson Huang /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */ 6563c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) { 657f085f672SAnson Huang regmap_write(map, IMX6_MISC1 + REG_CLR, 658f085f672SAnson Huang IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW 659f085f672SAnson Huang | IMX6_MISC1_IRQ_TEMPPANIC); 6603c94f17eSAnson Huang /* 6613c94f17eSAnson Huang * reset value of LOW ALARM is incorrect, set it to lowest 6623c94f17eSAnson Huang * value to avoid false trigger of low alarm. 6633c94f17eSAnson Huang */ 664f085f672SAnson Huang regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET, 665f085f672SAnson Huang data->socdata->low_alarm_mask); 6663c94f17eSAnson Huang } 6673c94f17eSAnson Huang 66837713a1eSPhilipp Zabel data->irq = platform_get_irq(pdev, 0); 66937713a1eSPhilipp Zabel if (data->irq < 0) 67037713a1eSPhilipp Zabel return data->irq; 67137713a1eSPhilipp Zabel 672ca3de46bSShawn Guo platform_set_drvdata(pdev, data); 673ca3de46bSShawn Guo 674ae621557SLeonard Crestez if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) { 675ae621557SLeonard Crestez ret = imx_init_from_nvmem_cells(pdev); 6765f3c0200SAnson Huang if (ret) 6775f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret, 6785f3c0200SAnson Huang "failed to init from nvmem\n"); 679ae621557SLeonard Crestez } else { 680ae621557SLeonard Crestez ret = imx_init_from_tempmon_data(pdev); 681ae621557SLeonard Crestez if (ret) { 682337a4aecSAnson Huang dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n"); 683ae621557SLeonard Crestez return ret; 684ae621557SLeonard Crestez } 685ca3de46bSShawn Guo } 686ca3de46bSShawn Guo 687ca3de46bSShawn Guo /* Make sure sensor is in known good state for measurements */ 688f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 689f085f672SAnson Huang data->socdata->power_down_mask); 690f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 691f085f672SAnson Huang data->socdata->measure_temp_mask); 692f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, 693f085f672SAnson Huang data->socdata->measure_freq_mask); 694f085f672SAnson Huang if (data->socdata->version != TEMPMON_IMX7D) 695f085f672SAnson Huang regmap_write(map, IMX6_MISC0 + REG_SET, 696f085f672SAnson Huang IMX6_MISC0_REFTOP_SELBIASOFF); 697f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 698f085f672SAnson Huang data->socdata->power_down_mask); 699ca3de46bSShawn Guo 700a1d00154SBastian Stender ret = imx_thermal_register_legacy_cooling(data); 7015f3c0200SAnson Huang if (ret) 7025f3c0200SAnson Huang return dev_err_probe(&pdev->dev, ret, 7035f3c0200SAnson Huang "failed to register cpufreq cooling device\n"); 704ca3de46bSShawn Guo 70590a21ff5SHeiner Kallweit data->thermal_clk = devm_clk_get(&pdev->dev, NULL); 70690a21ff5SHeiner Kallweit if (IS_ERR(data->thermal_clk)) { 70790a21ff5SHeiner Kallweit ret = PTR_ERR(data->thermal_clk); 70890a21ff5SHeiner Kallweit if (ret != -EPROBE_DEFER) 70990a21ff5SHeiner Kallweit dev_err(&pdev->dev, 71090a21ff5SHeiner Kallweit "failed to get thermal clk: %d\n", ret); 711c589c566SAnson Huang goto legacy_cleanup; 71290a21ff5SHeiner Kallweit } 71390a21ff5SHeiner Kallweit 71490a21ff5SHeiner Kallweit /* 71590a21ff5SHeiner Kallweit * Thermal sensor needs clk on to get correct value, normally 71690a21ff5SHeiner Kallweit * we should enable its clk before taking measurement and disable 71790a21ff5SHeiner Kallweit * clk after measurement is done, but if alarm function is enabled, 71890a21ff5SHeiner Kallweit * hardware will auto measure the temperature periodically, so we 71990a21ff5SHeiner Kallweit * need to keep the clk always on for alarm function. 72090a21ff5SHeiner Kallweit */ 72190a21ff5SHeiner Kallweit ret = clk_prepare_enable(data->thermal_clk); 72290a21ff5SHeiner Kallweit if (ret) { 72390a21ff5SHeiner Kallweit dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret); 724c589c566SAnson Huang goto legacy_cleanup; 72590a21ff5SHeiner Kallweit } 72690a21ff5SHeiner Kallweit 72730233a22SDaniel Lezcano data->tz = thermal_zone_device_register_with_trips("imx_thermal_zone", 72830233a22SDaniel Lezcano trips, 72930233a22SDaniel Lezcano ARRAY_SIZE(trips), 730017e5142SPhilipp Zabel BIT(IMX_TRIP_PASSIVE), data, 731ca3de46bSShawn Guo &imx_tz_ops, NULL, 732ca3de46bSShawn Guo IMX_PASSIVE_DELAY, 733ca3de46bSShawn Guo IMX_POLLING_DELAY); 734ca3de46bSShawn Guo if (IS_ERR(data->tz)) { 735ca3de46bSShawn Guo ret = PTR_ERR(data->tz); 736ca3de46bSShawn Guo dev_err(&pdev->dev, 737ca3de46bSShawn Guo "failed to register thermal zone device %d\n", ret); 738b6ad3981SAnson Huang goto clk_disable; 739ca3de46bSShawn Guo } 740ca3de46bSShawn Guo 741a2291badSTim Harvey dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC" 742a2291badSTim Harvey " critical:%dC passive:%dC\n", data->temp_grade, 74330233a22SDaniel Lezcano data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000, 74430233a22SDaniel Lezcano trips[IMX_TRIP_PASSIVE].temperature / 1000); 745a2291badSTim Harvey 74637713a1eSPhilipp Zabel /* Enable measurements at ~ 10 Hz */ 747f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, 748f085f672SAnson Huang data->socdata->measure_freq_mask); 74937713a1eSPhilipp Zabel measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ 750f085f672SAnson Huang regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET, 751f085f672SAnson Huang measure_freq << data->socdata->measure_freq_shift); 75230233a22SDaniel Lezcano imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature); 7533c94f17eSAnson Huang 7543c94f17eSAnson Huang if (data->socdata->version == TEMPMON_IMX6SX) 75530233a22SDaniel Lezcano imx_set_panic_temp(data, trips[IMX_TRIP_CRITICAL].temperature); 7563c94f17eSAnson Huang 757f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, 758f085f672SAnson Huang data->socdata->power_down_mask); 759f085f672SAnson Huang regmap_write(map, data->socdata->sensor_ctrl + REG_SET, 760f085f672SAnson Huang data->socdata->measure_temp_mask); 7614cf2ddf1SOleksij Rempel /* After power up, we need a delay before first access can be done. */ 7624cf2ddf1SOleksij Rempel usleep_range(20, 50); 7634cf2ddf1SOleksij Rempel 7644cf2ddf1SOleksij Rempel /* the core was configured and enabled just before */ 7654cf2ddf1SOleksij Rempel pm_runtime_set_active(&pdev->dev); 7664cf2ddf1SOleksij Rempel pm_runtime_enable(data->dev); 7674cf2ddf1SOleksij Rempel 7684cf2ddf1SOleksij Rempel ret = pm_runtime_resume_and_get(data->dev); 7694cf2ddf1SOleksij Rempel if (ret < 0) 7704cf2ddf1SOleksij Rempel goto disable_runtime_pm; 77137713a1eSPhilipp Zabel 772cf1ba1d7SMikhail Lappo data->irq_enabled = true; 7737f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_enable(data->tz); 7747f4957beSAndrzej Pietrasiewicz if (ret) 7757f4957beSAndrzej Pietrasiewicz goto thermal_zone_unregister; 776cf1ba1d7SMikhail Lappo 77784866ee5SBai Ping ret = devm_request_threaded_irq(&pdev->dev, data->irq, 77884866ee5SBai Ping imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, 77984866ee5SBai Ping 0, "imx_thermal", data); 78084866ee5SBai Ping if (ret < 0) { 78184866ee5SBai Ping dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); 782b6ad3981SAnson Huang goto thermal_zone_unregister; 78384866ee5SBai Ping } 78484866ee5SBai Ping 7854cf2ddf1SOleksij Rempel pm_runtime_put(data->dev); 7864cf2ddf1SOleksij Rempel 787ca3de46bSShawn Guo return 0; 788b6ad3981SAnson Huang 789b6ad3981SAnson Huang thermal_zone_unregister: 790b6ad3981SAnson Huang thermal_zone_device_unregister(data->tz); 7914cf2ddf1SOleksij Rempel disable_runtime_pm: 7924cf2ddf1SOleksij Rempel pm_runtime_put_noidle(data->dev); 7934cf2ddf1SOleksij Rempel pm_runtime_disable(data->dev); 794b6ad3981SAnson Huang clk_disable: 795b6ad3981SAnson Huang clk_disable_unprepare(data->thermal_clk); 796c589c566SAnson Huang legacy_cleanup: 797c589c566SAnson Huang imx_thermal_unregister_legacy_cooling(data); 798b6ad3981SAnson Huang 799b6ad3981SAnson Huang return ret; 800ca3de46bSShawn Guo } 801ca3de46bSShawn Guo 802ca3de46bSShawn Guo static int imx_thermal_remove(struct platform_device *pdev) 803ca3de46bSShawn Guo { 804ca3de46bSShawn Guo struct imx_thermal_data *data = platform_get_drvdata(pdev); 80537713a1eSPhilipp Zabel 8064cf2ddf1SOleksij Rempel pm_runtime_put_noidle(data->dev); 8074cf2ddf1SOleksij Rempel pm_runtime_disable(data->dev); 808ca3de46bSShawn Guo 809ca3de46bSShawn Guo thermal_zone_device_unregister(data->tz); 8109db11010SAnson Huang imx_thermal_unregister_legacy_cooling(data); 811ca3de46bSShawn Guo 812ca3de46bSShawn Guo return 0; 813ca3de46bSShawn Guo } 814ca3de46bSShawn Guo 815b009514fSAnson Huang static int __maybe_unused imx_thermal_suspend(struct device *dev) 816ca3de46bSShawn Guo { 817ca3de46bSShawn Guo struct imx_thermal_data *data = dev_get_drvdata(dev); 8187f4957beSAndrzej Pietrasiewicz int ret; 819ca3de46bSShawn Guo 820ca3de46bSShawn Guo /* 821b46cce59SAnson Huang * Need to disable thermal sensor, otherwise, when thermal core 822b46cce59SAnson Huang * try to get temperature before thermal sensor resume, a wrong 823b46cce59SAnson Huang * temperature will be read as the thermal sensor is powered 824f5e50bf4SAndrzej Pietrasiewicz * down. This is done in change_mode() operation called from 8257f4957beSAndrzej Pietrasiewicz * thermal_zone_device_disable() 826ca3de46bSShawn Guo */ 8277f4957beSAndrzej Pietrasiewicz ret = thermal_zone_device_disable(data->tz); 8287f4957beSAndrzej Pietrasiewicz if (ret) 8297f4957beSAndrzej Pietrasiewicz return ret; 830ca3de46bSShawn Guo 8314cf2ddf1SOleksij Rempel return pm_runtime_force_suspend(data->dev); 832ca3de46bSShawn Guo } 833ca3de46bSShawn Guo 834b009514fSAnson Huang static int __maybe_unused imx_thermal_resume(struct device *dev) 835ca3de46bSShawn Guo { 836b46cce59SAnson Huang struct imx_thermal_data *data = dev_get_drvdata(dev); 837e3bdc8d7SArvind Yadav int ret; 838b46cce59SAnson Huang 8394cf2ddf1SOleksij Rempel ret = pm_runtime_force_resume(data->dev); 840e3bdc8d7SArvind Yadav if (ret) 841e3bdc8d7SArvind Yadav return ret; 842b46cce59SAnson Huang /* Enabled thermal sensor after resume */ 8434cf2ddf1SOleksij Rempel return thermal_zone_device_enable(data->tz); 8444cf2ddf1SOleksij Rempel } 8454cf2ddf1SOleksij Rempel 8464cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev) 8474cf2ddf1SOleksij Rempel { 8484cf2ddf1SOleksij Rempel struct imx_thermal_data *data = dev_get_drvdata(dev); 8494cf2ddf1SOleksij Rempel const struct thermal_soc_data *socdata = data->socdata; 8504cf2ddf1SOleksij Rempel struct regmap *map = data->tempmon; 8514cf2ddf1SOleksij Rempel int ret; 8524cf2ddf1SOleksij Rempel 8534cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR, 8544cf2ddf1SOleksij Rempel socdata->measure_temp_mask); 8557f4957beSAndrzej Pietrasiewicz if (ret) 8567f4957beSAndrzej Pietrasiewicz return ret; 857b46cce59SAnson Huang 8584cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_SET, 8594cf2ddf1SOleksij Rempel socdata->power_down_mask); 8604cf2ddf1SOleksij Rempel if (ret) 8614cf2ddf1SOleksij Rempel return ret; 8624cf2ddf1SOleksij Rempel 8634cf2ddf1SOleksij Rempel clk_disable_unprepare(data->thermal_clk); 8644cf2ddf1SOleksij Rempel 865ca3de46bSShawn Guo return 0; 866ca3de46bSShawn Guo } 867ca3de46bSShawn Guo 8684cf2ddf1SOleksij Rempel static int __maybe_unused imx_thermal_runtime_resume(struct device *dev) 8694cf2ddf1SOleksij Rempel { 8704cf2ddf1SOleksij Rempel struct imx_thermal_data *data = dev_get_drvdata(dev); 8714cf2ddf1SOleksij Rempel const struct thermal_soc_data *socdata = data->socdata; 8724cf2ddf1SOleksij Rempel struct regmap *map = data->tempmon; 8734cf2ddf1SOleksij Rempel int ret; 8744cf2ddf1SOleksij Rempel 8754cf2ddf1SOleksij Rempel ret = clk_prepare_enable(data->thermal_clk); 8764cf2ddf1SOleksij Rempel if (ret) 8774cf2ddf1SOleksij Rempel return ret; 8784cf2ddf1SOleksij Rempel 8794cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR, 8804cf2ddf1SOleksij Rempel socdata->power_down_mask); 8814cf2ddf1SOleksij Rempel if (ret) 8824cf2ddf1SOleksij Rempel return ret; 8834cf2ddf1SOleksij Rempel 8844cf2ddf1SOleksij Rempel ret = regmap_write(map, socdata->sensor_ctrl + REG_SET, 8854cf2ddf1SOleksij Rempel socdata->measure_temp_mask); 8864cf2ddf1SOleksij Rempel if (ret) 8874cf2ddf1SOleksij Rempel return ret; 8884cf2ddf1SOleksij Rempel 8894cf2ddf1SOleksij Rempel /* 8904cf2ddf1SOleksij Rempel * According to the temp sensor designers, it may require up to ~17us 8914cf2ddf1SOleksij Rempel * to complete a measurement. 8924cf2ddf1SOleksij Rempel */ 8934cf2ddf1SOleksij Rempel usleep_range(20, 50); 8944cf2ddf1SOleksij Rempel 8954cf2ddf1SOleksij Rempel return 0; 8964cf2ddf1SOleksij Rempel } 8974cf2ddf1SOleksij Rempel 8984cf2ddf1SOleksij Rempel static const struct dev_pm_ops imx_thermal_pm_ops = { 8994cf2ddf1SOleksij Rempel SET_SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume) 9004cf2ddf1SOleksij Rempel SET_RUNTIME_PM_OPS(imx_thermal_runtime_suspend, 9014cf2ddf1SOleksij Rempel imx_thermal_runtime_resume, NULL) 9024cf2ddf1SOleksij Rempel }; 903ca3de46bSShawn Guo 904ca3de46bSShawn Guo static struct platform_driver imx_thermal = { 905ca3de46bSShawn Guo .driver = { 906ca3de46bSShawn Guo .name = "imx_thermal", 907ca3de46bSShawn Guo .pm = &imx_thermal_pm_ops, 908ca3de46bSShawn Guo .of_match_table = of_imx_thermal_match, 909ca3de46bSShawn Guo }, 910ca3de46bSShawn Guo .probe = imx_thermal_probe, 911ca3de46bSShawn Guo .remove = imx_thermal_remove, 912ca3de46bSShawn Guo }; 913ca3de46bSShawn Guo module_platform_driver(imx_thermal); 914ca3de46bSShawn Guo 915ca3de46bSShawn Guo MODULE_AUTHOR("Freescale Semiconductor, Inc."); 916ca3de46bSShawn Guo MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); 917ca3de46bSShawn Guo MODULE_LICENSE("GPL v2"); 918ca3de46bSShawn Guo MODULE_ALIAS("platform:imx-thermal"); 919