diff options
Diffstat (limited to 'drivers/thermal/imx_thermal.c')
-rw-r--r-- | drivers/thermal/imx_thermal.c | 560 |
1 files changed, 450 insertions, 110 deletions
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index b7cb49afa056..de4fc8791c6c 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -1,16 +1,18 @@ /* - * Copyright 2013 Freescale Semiconductor, Inc. + * Copyright 2013-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ - +#include <linux/busfreq-imx.h> #include <linux/clk.h> #include <linux/cpu_cooling.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/device_cooling.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/io.h> @@ -37,17 +39,20 @@ #define MISC1_IRQ_TEMPLOW (1 << 28) #define MISC1_IRQ_TEMPPANIC (1 << 27) -#define TEMPSENSE0 0x0180 -#define TEMPSENSE0_ALARM_VALUE_SHIFT 20 -#define TEMPSENSE0_ALARM_VALUE_MASK (0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT) -#define TEMPSENSE0_TEMP_CNT_SHIFT 8 -#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT) -#define TEMPSENSE0_FINISHED (1 << 2) -#define TEMPSENSE0_MEASURE_TEMP (1 << 1) -#define TEMPSENSE0_POWER_DOWN (1 << 0) - -#define TEMPSENSE1 0x0190 -#define TEMPSENSE1_MEASURE_FREQ 0xffff +/* i.MX6 specific */ +#define IMX6_TEMPSENSE0 0X180 +#define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT 20 +#define IMX6_TEMPSENSE0_ALARM_VALUE_MASK (0xfff << 20) +#define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT 8 +#define IMX6_TEMPSENSE0_TEMP_CNT_MASK (0xfff << 8) +#define IMX6_TEMPSENSE0_FINISHED (1 << 2) +#define IMX6_TEMPSENSE0_MEASURE_TEMP (1 << 1) +#define IMX6_TEMPSENSE0_POWER_DOWN (1 << 0) + +#define IMX6_TEMPSENSE1 0X190 +#define IMX6_TEMPSENSE1_MEASURE_FREQ 0xffff +#define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT 0 + /* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */ #define TEMPSENSE2 0x0290 #define TEMPSENSE2_LOW_VALUE_SHIFT 0 @@ -58,6 +63,29 @@ #define OCOTP_MEM0 0x0480 #define OCOTP_ANA1 0x04e0 +/* i.MX7D specific */ +#define IMX7_ANADIG_DIGPROG 0x800 +#define IMX7_TEMPSENSE0 0X300 +#define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT 18 +#define IMX7_TEMPSENSE0_PANIC_ALARM_MASK (0x1ff << 18) +#define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT 9 +#define IMX7_TEMPSENSE0_HIGH_ALARM_MASK (0x1ff << 9) +#define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT 0 +#define IMX7_TEMPSENSE0_LOW_ALARM_MASK 0x1ff + +#define IMX7_TEMPSENSE1 0X310 +#define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT 16 +#define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK (0xffff << 16) +#define IMX7_TEMPSENSE1_FINISHED (1 << 11) +#define IMX7_TEMPSENSE1_MEASURE_TEMP (1 << 10) +#define IMX7_TEMPSENSE1_POWER_DOWN (1 << 9) +#define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT 0 +#define IMX7_TEMPSENSE1_TEMP_VALUE_MASK 0x1ff + +#define IMX6_OCOTP_ANA1 0x04e0 +#define IMX7_OCOTP_ANA1 0x04f0 +#define IMX7_OCOTP_TESTER3 0x0440 + /* The driver supports 1 passive trip point and 1 critical trip point */ enum imx_thermal_trip { IMX_TRIP_PASSIVE, @@ -65,31 +93,133 @@ enum imx_thermal_trip { IMX_TRIP_NUM, }; +#define IMX_TEMP_PASSIVE_COOL_DELTA 10000 + #define IMX_POLLING_DELAY 2000 /* millisecond */ #define IMX_PASSIVE_DELAY 1000 #define FACTOR0 10000000 -#define FACTOR1 15976 -#define FACTOR2 4297157 +#define FACTOR1 15423 +#define FACTOR2 4148468 +#define OFFSET 3580661 #define TEMPMON_IMX6Q 1 #define TEMPMON_IMX6SX 2 +#define TEMPMON_IMX7 3 + +/* the register offsets and bitfields may change across + * i.MX SOCs, use below struct as a description of the + * register. + */ struct thermal_soc_data { + u32 sensor_ctrl; /* tempmon sensor basic control */ + u32 power_down_mask; + u32 measure_temp_mask; + + u32 measure_freq_ctrl; + u32 measure_freq_mask; + u32 measure_freq_shift; + + u32 temp_data; + u32 temp_value_mask; + u32 temp_value_shift; + u32 temp_valid_mask; + + u32 panic_alarm_ctrl; + u32 panic_alarm_mask; + u32 panic_alarm_shift; + + u32 high_alarm_ctrl; + u32 high_alarm_mask; + u32 high_alarm_shift; + + u32 low_alarm_ctrl; + u32 low_alarm_mask; + u32 low_alarm_shift; + u32 version; }; static struct thermal_soc_data thermal_imx6q_data = { .version = TEMPMON_IMX6Q, + + .sensor_ctrl = IMX6_TEMPSENSE0, + .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, + .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, + + .measure_freq_ctrl = IMX6_TEMPSENSE1, + .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, + .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, + + .temp_data = IMX6_TEMPSENSE0, + .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, + .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, + .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, + + .high_alarm_ctrl = IMX6_TEMPSENSE0, + .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, + .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, }; static struct thermal_soc_data thermal_imx6sx_data = { .version = TEMPMON_IMX6SX, + + .sensor_ctrl = IMX6_TEMPSENSE0, + .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, + .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, + + .measure_freq_ctrl = IMX6_TEMPSENSE1, + .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, + .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, + + .temp_data = IMX6_TEMPSENSE0, + .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, + .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, + .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, + + .high_alarm_ctrl = IMX6_TEMPSENSE0, + .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, + .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, + + .panic_alarm_ctrl = TEMPSENSE2, + .panic_alarm_mask = TEMPSENSE2_PANIC_VALUE_MASK, + .panic_alarm_shift = TEMPSENSE2_PANIC_VALUE_SHIFT, +}; + +static struct thermal_soc_data thermal_imx7d_data = { + .version = TEMPMON_IMX7, + + .sensor_ctrl = IMX7_TEMPSENSE1, + .power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN, + .measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP, + + .measure_freq_ctrl = IMX7_TEMPSENSE1, + .measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT, + .measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK, + + .temp_data = IMX7_TEMPSENSE1, + .temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK, + .temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT, + .temp_valid_mask = IMX7_TEMPSENSE1_FINISHED, + + .panic_alarm_ctrl = IMX7_TEMPSENSE1, + .panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK, + .panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT, + + .high_alarm_ctrl = IMX7_TEMPSENSE0, + .high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK, + .high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT, + + .low_alarm_ctrl = IMX7_TEMPSENSE0, + .low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK, + .low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT, + }; struct imx_thermal_data { struct thermal_zone_device *tz; - struct thermal_cooling_device *cdev; + struct thermal_cooling_device *cdev[2]; enum thermal_device_mode mode; struct regmap *tempmon; u32 c1, c2; /* See formula in imx_get_sensor_data() */ @@ -101,55 +231,78 @@ struct imx_thermal_data { bool irq_enabled; int irq; struct clk *thermal_clk; + struct mutex mutex; const struct thermal_soc_data *socdata; const char *temp_grade; }; +static struct imx_thermal_data *imx_thermal_data; +static int skip_finish_check; +static u32 imx7_lpsr_save[2]; + static void imx_set_panic_temp(struct imx_thermal_data *data, int panic_temp) { + const struct thermal_soc_data *soc_data = data->socdata; struct regmap *map = data->tempmon; int critical_value; - critical_value = (data->c2 - panic_temp) / data->c1; - regmap_write(map, TEMPSENSE2 + REG_CLR, TEMPSENSE2_PANIC_VALUE_MASK); - regmap_write(map, TEMPSENSE2 + REG_SET, critical_value << - TEMPSENSE2_PANIC_VALUE_SHIFT); + if (data->socdata->version == TEMPMON_IMX7) + critical_value = panic_temp / 1000 + data->c1 - 25; + else + critical_value = (data->c2 - panic_temp) / data->c1; + + regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR, + soc_data->panic_alarm_mask); + regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET, + critical_value << soc_data->panic_alarm_shift); } static void imx_set_alarm_temp(struct imx_thermal_data *data, int alarm_temp) { + const struct thermal_soc_data *soc_data = data->socdata; struct regmap *map = data->tempmon; int alarm_value; data->alarm_temp = alarm_temp; - alarm_value = (data->c2 - alarm_temp) / data->c1; - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_ALARM_VALUE_MASK); - regmap_write(map, TEMPSENSE0 + REG_SET, alarm_value << - TEMPSENSE0_ALARM_VALUE_SHIFT); + + if (data->socdata->version == TEMPMON_IMX7) + alarm_value = alarm_temp / 1000 + data->c1 - 25; + else + alarm_value = (data->c2 - alarm_temp) / data->c1; + + regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR, + soc_data->high_alarm_mask); + regmap_write(map, soc_data->high_alarm_ctrl + REG_SET, + alarm_value << soc_data->high_alarm_shift); } static int imx_get_temp(struct thermal_zone_device *tz, int *temp) { struct imx_thermal_data *data = tz->devdata; + const struct thermal_soc_data *soc_data = data->socdata; struct regmap *map = data->tempmon; unsigned int n_meas; bool wait; u32 val; + mutex_lock(&data->mutex); if (data->mode == THERMAL_DEVICE_ENABLED) { /* Check if a measurement is currently in progress */ - regmap_read(map, TEMPSENSE0, &val); - wait = !(val & TEMPSENSE0_FINISHED); + regmap_read(map, soc_data->temp_data, &val); + wait = !(val & soc_data->temp_valid_mask); } else { /* * Every time we measure the temperature, we will power on the * temperature sensor, enable measurements, take a reading, * disable measurements, power off the temperature sensor. */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + clk_prepare_enable(data->thermal_clk); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->power_down_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->measure_temp_mask); wait = true; } @@ -158,25 +311,41 @@ static int imx_get_temp(struct thermal_zone_device *tz, int *temp) * According to the temp sensor designers, it may require up to ~17us * to complete a measurement. */ - if (wait) - usleep_range(20, 50); - - regmap_read(map, TEMPSENSE0, &val); + if (wait) { + /* + * On i.MX7 TO1.0, the finish bit can only keep 1us after + * the measured data available. It is hard for software to + * polling this bit. So wait for 20ms to make sure the + * measured data is valid. + */ + if (data->socdata->version == TEMPMON_IMX7 && skip_finish_check) + msleep(20); + else + usleep_range(20, 50); + regmap_read(map, soc_data->temp_data, &val); + } if (data->mode != THERMAL_DEVICE_ENABLED) { - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->measure_temp_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->power_down_mask); + clk_disable_unprepare(data->thermal_clk); } - if ((val & TEMPSENSE0_FINISHED) == 0) { + if (!skip_finish_check && ((val & soc_data->temp_valid_mask) == 0)) { dev_dbg(&tz->device, "temp measurement never finished\n"); + mutex_unlock(&data->mutex); return -EAGAIN; } - n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT; - + n_meas = (val & soc_data->temp_value_mask) >> soc_data->temp_value_shift; /* See imx_get_sensor_data() for formula derivation */ *temp = data->c2 - n_meas * data->c1; + if (data->socdata->version == TEMPMON_IMX7) + *temp = (n_meas - data->c1 + 25) * 1000; + else + *temp = data->c2 - n_meas * data->c1; /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */ if (data->socdata->version == TEMPMON_IMX6Q) { @@ -201,6 +370,7 @@ static int imx_get_temp(struct thermal_zone_device *tz, int *temp) data->irq_enabled = true; enable_irq(data->irq); } + mutex_unlock(&data->mutex); return 0; } @@ -219,22 +389,26 @@ static int imx_set_mode(struct thermal_zone_device *tz, enum thermal_device_mode mode) { struct imx_thermal_data *data = tz->devdata; + const struct thermal_soc_data *soc_data = data->socdata; struct regmap *map = data->tempmon; if (mode == THERMAL_DEVICE_ENABLED) { tz->polling_delay = IMX_POLLING_DELAY; tz->passive_delay = IMX_PASSIVE_DELAY; - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); - + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->power_down_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->measure_temp_mask); if (!data->irq_enabled) { data->irq_enabled = true; enable_irq(data->irq); } } else { - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->measure_temp_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->power_down_mask); tz->polling_delay = 0; tz->passive_delay = 0; @@ -282,17 +456,18 @@ static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, { struct imx_thermal_data *data = tz->devdata; - /* do not allow changing critical threshold */ - if (trip == IMX_TRIP_CRITICAL) - return -EPERM; - - /* do not allow passive to be set higher than critical */ - if (temp < 0 || temp > data->temp_critical) - return -EINVAL; - - data->temp_passive = temp; + if (trip == IMX_TRIP_CRITICAL) { + data->temp_critical = temp; + if (data->socdata->version == TEMPMON_IMX6SX) + imx_set_panic_temp(data, temp); + } - imx_set_alarm_temp(data, temp); + if (trip == IMX_TRIP_PASSIVE) { + if (temp > (data->temp_max - (1000 * 10))) + return -EINVAL; + data->temp_passive = temp; + imx_set_alarm_temp(data, temp); + } return 0; } @@ -332,6 +507,23 @@ static int imx_unbind(struct thermal_zone_device *tz, return 0; } +static int imx_get_trend(struct thermal_zone_device *tz, + int trip, enum thermal_trend *trend) +{ + int ret; + int trip_temp; + + ret = imx_get_trip_temp(tz, trip, &trip_temp); + if (ret < 0) + return ret; + + if (tz->temperature >= (trip_temp - IMX_TEMP_PASSIVE_COOL_DELTA)) + *trend = THERMAL_TREND_RAISE_FULL; + else + *trend = THERMAL_TREND_DROP_FULL; + + return 0; +} static struct thermal_zone_device_ops imx_tz_ops = { .bind = imx_bind, .unbind = imx_unbind, @@ -342,69 +534,102 @@ static struct thermal_zone_device_ops imx_tz_ops = { .get_trip_temp = imx_get_trip_temp, .get_crit_temp = imx_get_crit_temp, .set_trip_temp = imx_set_trip_temp, + .get_trend = imx_get_trend, }; -static int imx_get_sensor_data(struct platform_device *pdev) +static inline void imx6_calibrate_data(struct imx_thermal_data *data, u32 val) { - struct imx_thermal_data *data = platform_get_drvdata(pdev); - struct regmap *map; - int t1, n1; - int ret; - u32 val; + int t1, t2, n1, n2; u64 temp64; - - map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, - "fsl,tempmon-data"); - if (IS_ERR(map)) { - ret = PTR_ERR(map); - dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); - return ret; - } - - ret = regmap_read(map, OCOTP_ANA1, &val); - if (ret) { - dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); - return ret; - } - - if (val == 0 || val == ~0) { - dev_err(&pdev->dev, "invalid sensor calibration data\n"); - return -EINVAL; - } - /* * Sensor data layout: * [31:20] - sensor value @ 25C + * [19:8] - sensor value of hot + * [7:0] - hot temperature value * Use universal formula now and only need sensor value @ 25C * slope = 0.4297157 - (0.0015976 * 25C fuse) */ n1 = val >> 20; + n2 = (val & 0xfff00) >> 8; + t2 = val & 0xff; t1 = 25; /* t1 always 25C */ /* * Derived from linear interpolation: * slope = 0.4297157 - (0.0015976 * 25C fuse) * slope = (FACTOR2 - FACTOR1 * n1) / FACTOR0 + * offset = OFFSET / 1000000 * (Nmeas - n1) / (Tmeas - t1) = slope * We want to reduce this down to the minimum computation necessary * for each temperature read. Also, we want Tmeas in millicelsius * and we don't want to lose precision from integer division. So... - * Tmeas = (Nmeas - n1) / slope + t1 - * milli_Tmeas = 1000 * (Nmeas - n1) / slope + 1000 * t1 - * milli_Tmeas = -1000 * (n1 - Nmeas) / slope + 1000 * t1 + * Tmeas = (Nmeas - n1) / slope + t1 + offset + * milli_Tmeas = 1000 * (Nmeas - n1) / slope + 1000 * t1 + OFFSET / 1000 + * milli_Tmeas = -1000 * (n1 - Nmeas) / slope + 1000 * t1 + OFFSET /1000 * Let constant c1 = (-1000 / slope) - * milli_Tmeas = (n1 - Nmeas) * c1 + 1000 * t1 - * Let constant c2 = n1 *c1 + 1000 * t1 + * milli_Tmeas = (n1 - Nmeas) * c1 + 1000 * t1 + OFFSET / 1000 + * Let constant c2 = n1 *c1 + 1000 * t1 + OFFSET / 1000 * milli_Tmeas = c2 - Nmeas * c1 */ temp64 = FACTOR0; temp64 *= 1000; do_div(temp64, FACTOR1 * n1 - FACTOR2); data->c1 = temp64; - data->c2 = n1 * data->c1 + 1000 * t1; + temp64 = OFFSET; + do_div(temp64, 1000); + data->c2 = n1 * data->c1 + 1000 * t1 + temp64; +} + +/* + * On i.MX7, we only use the calibration data at 25C to get the temp, + * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C. + */ +static inline void imx7_calibrate_data(struct imx_thermal_data *data, u32 val) +{ + data->c1 = (val >> 9) & 0x1ff; +} + +static int imx_get_sensor_data(struct platform_device *pdev) +{ + struct imx_thermal_data *data = platform_get_drvdata(pdev); + struct regmap *map; + int ret; + u32 val; + + map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "fsl,tempmon-data"); + if (IS_ERR(map)) { + ret = PTR_ERR(map); + dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); + return ret; + } + + if (data->socdata->version == TEMPMON_IMX7) + ret = regmap_read(map, IMX7_OCOTP_ANA1, &val); + else + ret = regmap_read(map, IMX6_OCOTP_ANA1, &val); + + if (ret) { + dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); + return ret; + } + + if (val == 0 || val == ~0) { + dev_err(&pdev->dev, "invalid sensor calibration data\n"); + return -EINVAL; + } + + if (data->socdata->version == TEMPMON_IMX7) + imx7_calibrate_data(data, val); + else + imx6_calibrate_data(data, val); /* use OTP for thermal grade */ - ret = regmap_read(map, OCOTP_MEM0, &val); + if (data->socdata->version == TEMPMON_IMX7) + ret = regmap_read(map, IMX7_OCOTP_TESTER3, &val); + else + ret = regmap_read(map, OCOTP_MEM0, &val); + if (ret) { dev_err(&pdev->dev, "failed to read temp grade: %d\n", ret); return ret; @@ -465,20 +690,68 @@ static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) static const struct of_device_id of_imx_thermal_match[] = { { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, }, { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, }, + { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, }, { /* end */ } }; MODULE_DEVICE_TABLE(of, of_imx_thermal_match); +static int thermal_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + const struct thermal_soc_data *soc_data = imx_thermal_data->socdata; + struct regmap *map = imx_thermal_data->tempmon; + + mutex_lock(&imx_thermal_data->mutex); + + switch (event) { + /* + * In low_bus_freq_mode, the thermal sensor auto measurement + * can be disabled to low the power consumption. + */ + case LOW_BUSFREQ_ENTER: + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->measure_temp_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->power_down_mask); + imx_thermal_data->mode = THERMAL_DEVICE_DISABLED; + disable_irq(imx_thermal_data->irq); + clk_disable_unprepare(imx_thermal_data->thermal_clk); + break; + + /* Enabled thermal auto measurement when exiting low_bus_freq_mode */ + case LOW_BUSFREQ_EXIT: + clk_prepare_enable(imx_thermal_data->thermal_clk); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->power_down_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->measure_temp_mask); + imx_thermal_data->mode = THERMAL_DEVICE_ENABLED; + enable_irq(imx_thermal_data->irq); + break; + + default: + break; + } + mutex_unlock(&imx_thermal_data->mutex); + + return NOTIFY_OK; +} + +static struct notifier_block thermal_notifier = { + .notifier_call = thermal_notifier_event, +}; + static int imx_thermal_probe(struct platform_device *pdev) { struct imx_thermal_data *data; struct regmap *map; int measure_freq; - int ret; + int ret, revision; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; + imx_thermal_data = data; map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); if (IS_ERR(map)) { @@ -514,16 +787,29 @@ static int imx_thermal_probe(struct platform_device *pdev) return ret; } + /* + * for i.MX7D TO1.0, finish bit is not available, check the + * SOC revision to skip checking the finish bit status. + */ + regmap_read(map, IMX7_ANADIG_DIGPROG, &revision); + if ((revision & 0xff) == 0x10) + skip_finish_check = 1; + /* Make sure sensor is in known good state for measurements */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); - regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); - - data->cdev = cpufreq_cooling_register(cpu_present_mask); - if (IS_ERR(data->cdev)) { - ret = PTR_ERR(data->cdev); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->power_down_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->measure_temp_mask); + regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, + data->socdata->measure_freq_mask); + if (data->socdata->version != TEMPMON_IMX7) + regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->power_down_mask); + + data->cdev[0] = cpufreq_cooling_register(cpu_present_mask); + if (IS_ERR(data->cdev[0])) { + ret = PTR_ERR(data->cdev[0]); if (ret != -EPROBE_DEFER) dev_err(&pdev->dev, "failed to register cpufreq cooling device: %d\n", @@ -531,13 +817,25 @@ static int imx_thermal_probe(struct platform_device *pdev) return ret; } + data->cdev[1] = devfreq_cooling_register(); + if (IS_ERR(data->cdev[1])) { + ret = PTR_ERR(data->cdev[1]); + if (ret != -EPROBE_DEFER) { + dev_err(&pdev->dev, + "failed to register cpufreq cooling device: %d\n", + ret); + cpufreq_cooling_unregister(data->cdev[0]); + } + return ret; + } + data->thermal_clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(data->thermal_clk)) { ret = PTR_ERR(data->thermal_clk); if (ret != -EPROBE_DEFER) dev_err(&pdev->dev, "failed to get thermal clk: %d\n", ret); - cpufreq_cooling_unregister(data->cdev); + cpufreq_cooling_unregister(data->cdev[0]); return ret; } @@ -551,13 +849,15 @@ static int imx_thermal_probe(struct platform_device *pdev) ret = clk_prepare_enable(data->thermal_clk); if (ret) { dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret); - cpufreq_cooling_unregister(data->cdev); + cpufreq_cooling_unregister(data->cdev[0]); + devfreq_cooling_unregister(data->cdev[1]); return ret; } + mutex_init(&data->mutex); data->tz = thermal_zone_device_register("imx_thermal_zone", IMX_TRIP_NUM, - BIT(IMX_TRIP_PASSIVE), data, + (1 << IMX_TRIP_NUM) - 1, data, &imx_tz_ops, NULL, IMX_PASSIVE_DELAY, IMX_POLLING_DELAY); @@ -566,7 +866,8 @@ static int imx_thermal_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to register thermal zone device %d\n", ret); clk_disable_unprepare(data->thermal_clk); - cpufreq_cooling_unregister(data->cdev); + cpufreq_cooling_unregister(data->cdev[0]); + devfreq_cooling_unregister(data->cdev[1]); return ret; } @@ -576,16 +877,20 @@ static int imx_thermal_probe(struct platform_device *pdev) data->temp_passive / 1000); /* Enable measurements at ~ 10 Hz */ - regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); + regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, + data->socdata->measure_freq_mask); measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ - regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq); + regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET, + measure_freq << data->socdata->measure_freq_shift); imx_set_alarm_temp(data, data->temp_passive); if (data->socdata->version == TEMPMON_IMX6SX) imx_set_panic_temp(data, data->temp_critical); - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->power_down_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->measure_temp_mask); data->irq_enabled = true; data->mode = THERMAL_DEVICE_ENABLED; @@ -597,10 +902,15 @@ static int imx_thermal_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); clk_disable_unprepare(data->thermal_clk); thermal_zone_device_unregister(data->tz); - cpufreq_cooling_unregister(data->cdev); + cpufreq_cooling_unregister(data->cdev[0]); + devfreq_cooling_unregister(data->cdev[1]); return ret; } + /* register the busfreq notifier called in low bus freq */ + if (data->socdata->version != TEMPMON_IMX7) + register_busfreq_notifier(&thermal_notifier); + return 0; } @@ -610,12 +920,18 @@ static int imx_thermal_remove(struct platform_device *pdev) struct regmap *map = data->tempmon; /* Disable measurements */ - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->power_down_mask); if (!IS_ERR(data->thermal_clk)) clk_disable_unprepare(data->thermal_clk); + /* unregister the busfreq notifier called in low bus freq */ + if (data->socdata->version != TEMPMON_IMX7) + unregister_busfreq_notifier(&thermal_notifier); + thermal_zone_device_unregister(data->tz); - cpufreq_cooling_unregister(data->cdev); + cpufreq_cooling_unregister(data->cdev[0]); + devfreq_cooling_unregister(data->cdev[1]); return 0; } @@ -632,8 +948,20 @@ static int imx_thermal_suspend(struct device *dev) * temperature will be read as the thermal sensor is powered * down. */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->measure_temp_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->power_down_mask); + + /* + * Save the temp sensor registers of i.MX7D as the tempmon + * will lost power in LPSR mode + */ + if (data->socdata->version == TEMPMON_IMX7) { + regmap_read(map, data->socdata->sensor_ctrl, &imx7_lpsr_save[0]); + regmap_read(map, data->socdata->high_alarm_ctrl, &imx7_lpsr_save[1]); + } + data->mode = THERMAL_DEVICE_DISABLED; clk_disable_unprepare(data->thermal_clk); @@ -646,9 +974,21 @@ static int imx_thermal_resume(struct device *dev) struct regmap *map = data->tempmon; clk_prepare_enable(data->thermal_clk); + + /* + * restore the temp sensor registers of i.MX7D as the tempmon + * will lost power in LPSR mode + */ + if (data->socdata->version == TEMPMON_IMX7) { + regmap_write(map, data->socdata->sensor_ctrl, imx7_lpsr_save[0]); + regmap_write(map, data->socdata->high_alarm_ctrl, imx7_lpsr_save[1]); + } + /* Enabled thermal sensor after resume */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->power_down_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->measure_temp_mask); data->mode = THERMAL_DEVICE_ENABLED; return 0; |