| /* |
| * exynos_tmu.c - Samsung EXYNOS TMU (Thermal Management Unit) |
| * |
| * Copyright (C) 2014 Samsung Electronics |
| * Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com> |
| * Lukasz Majewski <l.majewski@samsung.com> |
| * |
| * Copyright (C) 2011 Samsung Electronics |
| * Donggeun Kim <dg77.kim@samsung.com> |
| * Amit Daniel Kachhap <amit.kachhap@linaro.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/platform_device.h> |
| #include <linux/cpufreq.h> |
| #include <linux/suspend.h> |
| #include <linux/pm_qos.h> |
| #include <linux/threads.h> |
| #include <linux/thermal.h> |
| #include <linux/gpu_cooling.h> |
| #include <linux/isp_cooling.h> |
| #include <linux/slab.h> |
| #include <linux/exynos-ss.h> |
| #include <linux/cpu.h> |
| #include <soc/samsung/tmu.h> |
| #include <soc/samsung/ect_parser.h> |
| #ifdef CONFIG_EXYNOS_MCINFO |
| #include <soc/samsung/exynos-mcinfo.h> |
| #endif |
| #include <dt-bindings/thermal/thermal_exynos.h> |
| |
| #include "exynos_tmu.h" |
| #include "../thermal_core.h" |
| |
| #ifdef CONFIG_SEC_EXT |
| #include <linux/sec_ext.h> |
| #endif |
| #ifdef CONFIG_SEC_PM |
| #include <linux/sec_sysfs.h> |
| #endif |
| |
| /* Exynos generic registers */ |
| #define EXYNOS_TMU_REG_TRIMINFO 0x0 |
| #define EXYNOS_TMU_REG_TRIMINFO1 0x4 |
| #define EXYNOS_TMU_REG_TRIMINFO2 0x8 |
| #define EXYNOS_TMU_REG_CONTROL 0x20 |
| #define EXYNOS_TMU_REG_CONTROL1 0x24 |
| #define EXYNOS_TMU_REG_STATUS 0x28 |
| #define EXYNOS_TMU_REG_CURRENT_TEMP1_0 0x40 |
| #define EXYNOS_TMU_REG_CURRENT_TEMP4_2 0x44 |
| #define EXYNOS_TMU_REG_CURRENT_TEMP7_5 0x48 |
| #define EXYNOS_TMU_REG_INTEN0 0x110 |
| #define EXYNOS_TMU_REG_INTEN5 0x310 |
| #define EXYNOS_TMU_REG_INTEN_OFFSET 0x10 |
| #define EXYNOS_TMU_REG_INTSTAT 0x74 |
| #define EXYNOS_TMU_REG_INTCLEAR 0x78 |
| |
| #define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24 |
| #define EXYNOS_TMU_REF_VOLTAGE_MASK 0x1f |
| #define EXYNOS_TMU_BUF_SLOPE_SEL_MASK 0xf |
| #define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT 8 |
| #define EXYNOS_TMU_CORE_EN_SHIFT 0 |
| #define EXYNOS_TMU_MUX_ADDR_SHIFT 20 |
| #define EXYNOS_TMU_MUX_ADDR_MASK 0x7 |
| #define EXYNOS_TMU_PTAT_CON_SHIFT 20 |
| #define EXYNOS_TMU_PTAT_CON_MASK 0x7 |
| #define EXYNOS_TMU_BUF_CONT_SHIFT 12 |
| #define EXYNOS_TMU_BUF_CONT_MASK 0xf |
| #define EXYNOS_TMU_ |
| |
| #define EXYNOS_TMU_TRIP_MODE_SHIFT 13 |
| #define EXYNOS_TMU_TRIP_MODE_MASK 0x7 |
| #define EXYNOS_TMU_THERM_TRIP_EN_SHIFT 12 |
| |
| #define EXYNOS_TMU_INTEN_RISE0_SHIFT 0 |
| #define EXYNOS_TMU_INTEN_FALL0_SHIFT 16 |
| |
| #define EXYNOS_EMUL_TIME 0x57F0 |
| #define EXYNOS_EMUL_TIME_MASK 0xffff |
| #define EXYNOS_EMUL_TIME_SHIFT 16 |
| #define EXYNOS_EMUL_DATA_SHIFT 7 |
| #define EXYNOS_EMUL_DATA_MASK 0x1FF |
| #define EXYNOS_EMUL_ENABLE 0x1 |
| |
| #define EXYNOS_THD_TEMP_RISE7_6 0x50 |
| #define EXYNOS_THD_TEMP_FALL7_6 0x60 |
| #define EXYNOS_THD_TEMP_R_OFFSET 0x100 |
| #define EXYNOS_THD_TEMP_RISE7_6_SHIFT 16 |
| #define EXYNOS_TMU_INTEN_RISE0_SHIFT 0 |
| #define EXYNOS_TMU_INTEN_RISE1_SHIFT 1 |
| #define EXYNOS_TMU_INTEN_RISE2_SHIFT 2 |
| #define EXYNOS_TMU_INTEN_RISE3_SHIFT 3 |
| #define EXYNOS_TMU_INTEN_RISE4_SHIFT 4 |
| #define EXYNOS_TMU_INTEN_RISE5_SHIFT 5 |
| #define EXYNOS_TMU_INTEN_RISE6_SHIFT 6 |
| #define EXYNOS_TMU_INTEN_RISE7_SHIFT 7 |
| |
| #define EXYNOS_TMU_CALIB_SEL_SHIFT (23) |
| #define EXYNOS_TMU_CALIB_SEL_MASK (0x1) |
| #define EXYNOS_TMU_TEMP_SHIFT (9) |
| #define EXYNOS_TMU_TEMP_MASK (0x1ff) |
| #define EXYNOS_TMU_TRIMINFO_85_P0_SHIFT (9) |
| #define EXYNOS_TRIMINFO_ONE_POINT_TRIMMING (0) |
| #define EXYNOS_TRIMINFO_TWO_POINT_TRIMMING (1) |
| #define EXYNOS_TMU_T_BUF_VREF_SEL_SHIFT (18) |
| #define EXYNOS_TMU_T_BUF_VREF_SEL_MASK (0x1F) |
| #define EXYNOS_TMU_T_PTAT_CONT_MASK (0x7) |
| #define EXYNOS_TMU_T_BUF_SLOPE_SEL_SHIFT (18) |
| #define EXYNOS_TMU_T_BUF_SLOPE_SEL_MASK (0xF) |
| #define EXYNOS_TMU_T_BUF_CONT_MASK (0xF) |
| |
| #define EXYNOS_TMU_REG_INTPEND0 (0x118) |
| #define EXYNOS_TMU_REG_INTPEND5 (0x318) |
| #define EXYNOS_TMU_REG_INTPEN_OFFSET (0x10) |
| #define EXYNOS_TMU_REG_EMUL_CON (0x160) |
| |
| #define EXYNOS_TMU_REG_AVG_CON (0x38) |
| #define EXYNOS_TMU_AVG_CON_SHIFT (18) |
| #define EXYNOS_TMU_AVG_CON_MASK (0x3) |
| #define EXYNOS_TMU_AVG_MODE_MASK (0x7) |
| #define EXYNOS_TMU_AVG_MODE_DEFAULT (0x0) |
| #define EXYNOS_TMU_AVG_MODE_2 (0x5) |
| #define EXYNOS_TMU_AVG_MODE_4 (0x6) |
| |
| #define EXYNOS_TMU_DEM_ENABLE (1) |
| #define EXYNOS_TMU_DEM_SHIFT (4) |
| |
| #define EXYNOS_TMU_REG_COUNTER_VALUE0 (0x30) |
| #define EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT (0) |
| #define EXYNOS_TMU_EN_TEMP_SEN_OFF_MASK (0xffff) |
| #define EXYNOS_TMU_REG_COUNTER_VALUE1 (0x34) |
| #define EXYNOS_TMU_CLK_SENSE_ON_SHIFT (16) |
| #define EXYNOS_TMU_CLK_SENSE_ON_MASK (0xffff) |
| #define EXYNOS_TMU_TEM1456X_SENSE_VALUE (0x0A28) |
| |
| #define EXYNOS_TMU_NUM_PROBE_SHIFT (16) |
| #define EXYNOS_TMU_NUM_PROBE_MASK (0x7) |
| |
| #define TOTAL_SENSORS 8 |
| #define DEFAULT_BALANCE_OFFSET 20 |
| |
| static bool suspended; |
| static bool is_cpu_hotplugged_out; |
| static DEFINE_MUTEX (thermal_suspend_lock); |
| |
| /* list of multiple instance for each thermal sensor */ |
| static LIST_HEAD(dtm_dev_list); |
| |
| static u32 global_avg_con; |
| |
| static void exynos_report_trigger(struct exynos_tmu_data *p) |
| { |
| struct thermal_zone_device *tz = p->tzd; |
| |
| if (!tz) { |
| pr_err("No thermal zone device defined\n"); |
| return; |
| } |
| |
| thermal_zone_device_update(tz); |
| } |
| |
| /* |
| * TMU treats temperature as a mapped temperature code. |
| * The temperature is converted differently depending on the calibration type. |
| */ |
| static int temp_to_code(struct exynos_tmu_data *data, u8 temp) |
| { |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| int temp_code; |
| |
| if (temp > EXYNOS_MAX_TEMP) |
| temp = EXYNOS_MAX_TEMP; |
| else if (temp < EXYNOS_MIN_TEMP) |
| temp = EXYNOS_MIN_TEMP; |
| |
| switch (pdata->cal_type) { |
| case TYPE_TWO_POINT_TRIMMING: |
| temp_code = (temp - pdata->first_point_trim) * |
| (data->temp_error2 - data->temp_error1) / |
| (pdata->second_point_trim - pdata->first_point_trim) + |
| data->temp_error1; |
| break; |
| case TYPE_ONE_POINT_TRIMMING: |
| temp_code = temp + data->temp_error1 - pdata->first_point_trim; |
| break; |
| default: |
| temp_code = temp + pdata->default_temp_offset; |
| break; |
| } |
| |
| return temp_code; |
| } |
| |
| /* |
| * TMU treats temperature with the index as a mapped temperature code. |
| * The temperature is converted differently depending on the calibration type. |
| */ |
| static int temp_to_code_with_sensorinfo(struct exynos_tmu_data *data, u16 temp, struct sensor_info *info) |
| { |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| int temp_code; |
| |
| if (temp > EXYNOS_MAX_TEMP) |
| temp = EXYNOS_MAX_TEMP; |
| else if (temp < EXYNOS_MIN_TEMP) |
| temp = EXYNOS_MIN_TEMP; |
| |
| switch (info->cal_type) { |
| case TYPE_TWO_POINT_TRIMMING: |
| temp_code = (temp - pdata->first_point_trim) * |
| (info->temp_error2 - info->temp_error1) / |
| (pdata->second_point_trim - pdata->first_point_trim) + |
| info->temp_error1; |
| break; |
| case TYPE_ONE_POINT_TRIMMING: |
| temp_code = temp + info->temp_error1 - pdata->first_point_trim; |
| break; |
| default: |
| temp_code = temp + pdata->default_temp_offset; |
| break; |
| } |
| |
| return temp_code; |
| } |
| |
| /* |
| * Calculate a temperature value from a temperature code. |
| * The unit of the temperature is degree Celsius. |
| */ |
| static int code_to_temp(struct exynos_tmu_data *data, u16 temp_code) |
| { |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| int temp; |
| |
| switch (pdata->cal_type) { |
| case TYPE_TWO_POINT_TRIMMING: |
| temp = (temp_code - data->temp_error1) * |
| (pdata->second_point_trim - pdata->first_point_trim) / |
| (data->temp_error2 - data->temp_error1) + |
| pdata->first_point_trim; |
| break; |
| case TYPE_ONE_POINT_TRIMMING: |
| temp = temp_code - data->temp_error1 + pdata->first_point_trim; |
| break; |
| default: |
| temp = temp_code - pdata->default_temp_offset; |
| break; |
| } |
| |
| /* temperature should range between minimum and maximum */ |
| if (temp > EXYNOS_MAX_TEMP) |
| temp = EXYNOS_MAX_TEMP; |
| else if (temp < EXYNOS_MIN_TEMP) |
| temp = EXYNOS_MIN_TEMP; |
| |
| return temp; |
| } |
| |
| /* |
| * Calculate a temperature value with the index from a temperature code. |
| * The unit of the temperature is degree Celsius. |
| */ |
| static int code_to_temp_with_sensorinfo(struct exynos_tmu_data *data, u16 temp_code, struct sensor_info *info) |
| { |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| int temp; |
| |
| switch (info->cal_type) { |
| case TYPE_TWO_POINT_TRIMMING: |
| temp = (temp_code - info->temp_error1) * |
| (pdata->second_point_trim - pdata->first_point_trim) / |
| (info->temp_error2 - info->temp_error1) + |
| pdata->first_point_trim; |
| break; |
| case TYPE_ONE_POINT_TRIMMING: |
| temp = temp_code - info->temp_error1 + pdata->first_point_trim; |
| break; |
| default: |
| temp = temp_code - pdata->default_temp_offset; |
| break; |
| } |
| |
| /* temperature should range between minimum and maximum */ |
| if (temp > EXYNOS_MAX_TEMP) |
| temp = EXYNOS_MAX_TEMP; |
| else if (temp < EXYNOS_MIN_TEMP) |
| temp = EXYNOS_MIN_TEMP; |
| |
| return temp; |
| } |
| |
| static int exynos_tmu_initialize(struct platform_device *pdev) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = data->tmu_initialize(pdev); |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static u32 get_con_reg(struct exynos_tmu_data *data, u32 con) |
| { |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| |
| con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT); |
| con |= pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT; |
| |
| con &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); |
| con |= (pdata->gain << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); |
| |
| if (pdata->noise_cancel_mode) { |
| con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << EXYNOS_TMU_TRIP_MODE_SHIFT); |
| con |= (pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT); |
| } |
| |
| return con; |
| } |
| |
| static void exynos_tmu_control(struct platform_device *pdev, bool on) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| |
| mutex_lock(&data->lock); |
| data->tmu_control(pdev, on); |
| mutex_unlock(&data->lock); |
| } |
| |
| static int exynos8890_tmu_initialize(struct platform_device *pdev) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct thermal_zone_device *tz = data->tzd; |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| unsigned int rising_threshold = 0, falling_threshold = 0; |
| int temp, temp_hist; |
| unsigned int trim_info; |
| unsigned int reg_off, bit_off; |
| int threshold_code, i; |
| |
| /* Check tmu core ready status */ |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); |
| |
| |
| /* Check thermal calibration type */ |
| pdata->cal_type = (trim_info >> EXYNOS_TMU_CALIB_SEL_SHIFT) |
| & EXYNOS_TMU_CALIB_SEL_MASK; |
| |
| /* Check temp_error1 and error2 value */ |
| data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; |
| data->temp_error2 = (trim_info >> EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) |
| & EXYNOS_TMU_TEMP_MASK; |
| |
| if (!data->temp_error1) |
| data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; |
| if (!data->temp_error2) |
| data->temp_error2 = (pdata->efuse_value >> |
| EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) |
| & EXYNOS_TMU_TEMP_MASK; |
| /* Write temperature code for rising and falling threshold */ |
| for (i = (of_thermal_get_ntrips(tz) - 1); i >= 0; i--) { |
| /* |
| * On exynos7 there are 4 rising and 4 falling threshold |
| * registers (0x50-0x5c and 0x60-0x6c respectively). Each |
| * register holds the value of two threshold levels (at bit |
| * offsets 0 and 16). Based on the fact that there are atmost |
| * eight possible trigger levels, calculate the register and |
| * bit offsets where the threshold levels are to be written. |
| * |
| * e.g. EXYNOS_THD_TEMP_RISE7_6 (0x50) |
| * [24:16] - Threshold level 7 |
| * [8:0] - Threshold level 6 |
| * e.g. EXYNOS_THD_TEMP_RISE5_4 (0x54) |
| * [24:16] - Threshold level 5 |
| * [8:0] - Threshold level 4 |
| * |
| * and similarly for falling thresholds. |
| * |
| * Based on the above, calculate the register and bit offsets |
| * for rising/falling threshold levels and populate them. |
| */ |
| reg_off = ((7 - i) / 2) * 4; |
| bit_off = ((8 - i) % 2); |
| |
| tz->ops->get_trip_temp(tz, i, &temp); |
| temp /= MCELSIUS; |
| |
| tz->ops->get_trip_hyst(tz, i, &temp_hist); |
| temp_hist = temp - (temp_hist / MCELSIUS); |
| |
| /* Set 9-bit temperature code for rising threshold levels */ |
| threshold_code = temp_to_code(data, temp); |
| rising_threshold = readl(data->base + |
| EXYNOS_THD_TEMP_RISE7_6 + reg_off); |
| rising_threshold &= ~(EXYNOS_TMU_TEMP_MASK << (16 * bit_off)); |
| rising_threshold |= threshold_code << (16 * bit_off); |
| writel(rising_threshold, |
| data->base + EXYNOS_THD_TEMP_RISE7_6 + reg_off); |
| |
| /* Set 9-bit temperature code for falling threshold levels */ |
| threshold_code = temp_to_code(data, temp_hist); |
| falling_threshold &= ~(EXYNOS_TMU_TEMP_MASK << (16 * bit_off)); |
| falling_threshold |= threshold_code << (16 * bit_off); |
| writel(falling_threshold, |
| data->base + EXYNOS_THD_TEMP_FALL7_6 + reg_off); |
| } |
| |
| data->tmu_clear_irqs(data); |
| |
| return 0; |
| } |
| |
| static void exynos8890_tmu_control(struct platform_device *pdev, bool on) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct thermal_zone_device *tz = data->tzd; |
| unsigned int con, interrupt_en, trim_info, trim_info1; |
| unsigned int t_buf_vref_sel, t_buf_slope_sel; |
| |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); |
| trim_info1 = readl(data->base + EXYNOS_TMU_REG_TRIMINFO1); |
| |
| /* Save fuse buf_vref_sel, calib_sel value to TRIMINFO and 1 register */ |
| t_buf_vref_sel = (trim_info >> EXYNOS_TMU_T_BUF_VREF_SEL_SHIFT) |
| & (EXYNOS_TMU_T_BUF_VREF_SEL_MASK); |
| t_buf_slope_sel = (trim_info1 >> EXYNOS_TMU_T_BUF_SLOPE_SEL_SHIFT) |
| & (EXYNOS_TMU_T_BUF_SLOPE_SEL_MASK); |
| |
| con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL)); |
| |
| if (on) { |
| con |= (t_buf_vref_sel << EXYNOS_TMU_REF_VOLTAGE_SHIFT); |
| con |= (t_buf_slope_sel << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); |
| con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| interrupt_en = |
| (of_thermal_is_trip_valid(tz, 7) |
| << EXYNOS_TMU_INTEN_RISE7_SHIFT) | |
| (of_thermal_is_trip_valid(tz, 6) |
| << EXYNOS_TMU_INTEN_RISE6_SHIFT) | |
| (of_thermal_is_trip_valid(tz, 5) |
| << EXYNOS_TMU_INTEN_RISE5_SHIFT) | |
| (of_thermal_is_trip_valid(tz, 4) |
| << EXYNOS_TMU_INTEN_RISE4_SHIFT) | |
| (of_thermal_is_trip_valid(tz, 3) |
| << EXYNOS_TMU_INTEN_RISE3_SHIFT) | |
| (of_thermal_is_trip_valid(tz, 2) |
| << EXYNOS_TMU_INTEN_RISE2_SHIFT) | |
| (of_thermal_is_trip_valid(tz, 1) |
| << EXYNOS_TMU_INTEN_RISE1_SHIFT) | |
| (of_thermal_is_trip_valid(tz, 0) |
| << EXYNOS_TMU_INTEN_RISE0_SHIFT); |
| |
| interrupt_en |= |
| interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; |
| } else { |
| con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con &= ~(1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| interrupt_en = 0; /* Disable all interrupts */ |
| } |
| |
| writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN0); |
| writel(con, data->base + EXYNOS_TMU_REG_CONTROL); |
| } |
| |
| static int exynos8895_tmu_initialize(struct platform_device *pdev) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct thermal_zone_device *tz = data->tzd; |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| unsigned int rising_threshold = 0, falling_threshold = 0; |
| int temp, temp_hist; |
| unsigned int trim_info; |
| unsigned int reg_off = 0, bit_off; |
| int threshold_code, i; |
| int sensor; |
| int count = 0, interrupt_count = 0; |
| struct sensor_info temp_info; |
| enum thermal_trip_type type; |
| |
| u16 cal_type; |
| u32 temp_error1; |
| u32 temp_error2; |
| |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); |
| cal_type = (trim_info >> EXYNOS_TMU_CALIB_SEL_SHIFT) & EXYNOS_TMU_CALIB_SEL_MASK; |
| |
| for (sensor = 0; sensor < TOTAL_SENSORS; sensor++) { |
| /* Read the sensor error value from TRIMINFOX */ |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO + 0x4 * sensor); |
| temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; |
| temp_error2 = (trim_info >> EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) & EXYNOS_TMU_TEMP_MASK; |
| |
| /* If the sensor is used, save its information. */ |
| if (data->sensors & (1 << sensor)) { |
| /* Save sensor id */ |
| data->sensor_info[count].sensor_num = sensor; |
| dev_info(&pdev->dev, "Sensor number = %d\n", sensor); |
| |
| /* Check thermal calibration type */ |
| data->sensor_info[count].cal_type = cal_type; |
| |
| /* Check temp_error1 value */ |
| data->sensor_info[count].temp_error1 = temp_error1; |
| if (!data->sensor_info[count].temp_error1) |
| data->sensor_info[count].temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; |
| |
| /* Check temp_error2 if calibration type is TYPE_TWO_POINT_TRIMMING */ |
| if (data->sensor_info[count].cal_type == TYPE_TWO_POINT_TRIMMING) { |
| data->sensor_info[count].temp_error2 = temp_error2; |
| |
| if (!data->sensor_info[count].temp_error2) |
| data->sensor_info[count].temp_error2 = (pdata->efuse_value >> EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) |
| & EXYNOS_TMU_TEMP_MASK; |
| } |
| } |
| |
| temp_info.cal_type = cal_type; |
| temp_info.temp_error1 = temp_error1; |
| temp_info.temp_error2 = temp_error2; |
| |
| if (data->sensors & (1 << sensor)) { |
| interrupt_count = 0; |
| /* Write temperature code for rising and falling threshold */ |
| for (i = (of_thermal_get_ntrips(tz) - 1); i >= 0; i--) { |
| /* |
| * On exynos8 there are 4 rising and 4 falling threshold |
| * registers (0x50-0x5c and 0x60-0x6c respectively). Each |
| * register holds the value of two threshold levels (at bit |
| * offsets 0 and 16). Based on the fact that there are atmost |
| * eight possible trigger levels, calculate the register and |
| * bit offsets where the threshold levels are to be written. |
| * |
| * e.g. EXYNOS_THD_TEMP_RISE7_6 (0x50) |
| * [24:16] - Threshold level 7 |
| * [8:0] - Threshold level 6 |
| * e.g. EXYNOS_THD_TEMP_RISE5_4 (0x54) |
| * [24:16] - Threshold level 5 |
| * [8:0] - Threshold level 4 |
| * |
| * and similarly for falling thresholds. |
| * |
| * Based on the above, calculate the register and bit offsets |
| * for rising/falling threshold levels and populate them. |
| */ |
| |
| tz->ops->get_trip_type(tz, i, &type); |
| |
| if (type == THERMAL_TRIP_PASSIVE) |
| continue; |
| |
| reg_off = (interrupt_count / 2) * 4; |
| bit_off = ((interrupt_count + 1) % 2); |
| |
| if (sensor > 0) |
| reg_off = reg_off + EXYNOS_THD_TEMP_R_OFFSET + sensor * 0x20; |
| |
| tz->ops->get_trip_temp(tz, i, &temp); |
| temp /= MCELSIUS; |
| |
| tz->ops->get_trip_hyst(tz, i, &temp_hist); |
| temp_hist = temp - (temp_hist / MCELSIUS); |
| |
| /* Set 9-bit temperature code for rising threshold levels */ |
| threshold_code = temp_to_code_with_sensorinfo(data, temp, &data->sensor_info[count]); |
| rising_threshold = readl(data->base + |
| EXYNOS_THD_TEMP_RISE7_6 + reg_off); |
| rising_threshold &= ~(EXYNOS_TMU_TEMP_MASK << (16 * bit_off)); |
| rising_threshold |= threshold_code << (16 * bit_off); |
| writel(rising_threshold, |
| data->base + EXYNOS_THD_TEMP_RISE7_6 + reg_off); |
| |
| /* Set 9-bit temperature code for falling threshold levels */ |
| threshold_code = temp_to_code_with_sensorinfo(data, temp_hist, &data->sensor_info[count]); |
| falling_threshold &= ~(EXYNOS_TMU_TEMP_MASK << (16 * bit_off)); |
| falling_threshold |= threshold_code << (16 * bit_off); |
| writel(falling_threshold, |
| data->base + EXYNOS_THD_TEMP_FALL7_6 + reg_off); |
| |
| interrupt_count++; |
| } |
| count++; |
| } |
| } |
| |
| data->tmu_clear_irqs(data); |
| |
| return 0; |
| } |
| |
| static void exynos8895_tmu_control(struct platform_device *pdev, bool on) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct thermal_zone_device *tz = data->tzd; |
| unsigned int con, con1, interrupt_en, trim_info, trim_info1, trim_info2; |
| unsigned int t_buf_vref_sel, t_buf_slope_sel; |
| unsigned int bit_off; |
| int i, interrupt_count = 0; |
| u32 avg_con, avg_sel; |
| u32 mux_val; |
| u32 counter_value0, counter_value1; |
| enum thermal_trip_type type; |
| |
| interrupt_en = 0; |
| con = readl(data->base + EXYNOS_TMU_REG_CONTROL); |
| con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con &= ~(1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| writel(con, data->base + EXYNOS_TMU_REG_CONTROL); |
| con = 0; |
| |
| con1 = readl(data->base + EXYNOS_TMU_REG_CONTROL1); |
| con1 &= ~(EXYNOS_TMU_NUM_PROBE_MASK << EXYNOS_TMU_NUM_PROBE_SHIFT); |
| con1 |= (data->num_probe << EXYNOS_TMU_NUM_PROBE_SHIFT); |
| writel(con1, data->base + EXYNOS_TMU_REG_CONTROL1); |
| |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); |
| trim_info1 = readl(data->base + EXYNOS_TMU_REG_TRIMINFO1); |
| trim_info2 = readl(data->base + EXYNOS_TMU_REG_TRIMINFO2); |
| |
| /* Save fuse buf_vref_sel, calib_sel value to TRIMINFO and 1 register */ |
| t_buf_vref_sel = (trim_info >> EXYNOS_TMU_T_BUF_VREF_SEL_SHIFT) |
| & (EXYNOS_TMU_T_BUF_VREF_SEL_MASK); |
| t_buf_slope_sel = (trim_info1 >> EXYNOS_TMU_T_BUF_SLOPE_SEL_SHIFT) |
| & (EXYNOS_TMU_T_BUF_SLOPE_SEL_MASK); |
| avg_sel = (trim_info2 >> EXYNOS_TMU_AVG_CON_SHIFT) & EXYNOS_TMU_AVG_CON_MASK; |
| |
| con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL)); |
| avg_con = readl(data->base + EXYNOS_TMU_REG_AVG_CON); |
| |
| if (avg_sel) { |
| avg_con |= ((avg_con & EXYNOS_TMU_AVG_MODE_MASK) | EXYNOS_TMU_AVG_MODE_DEFAULT); |
| avg_con &= ~(EXYNOS_TMU_DEM_ENABLE << EXYNOS_TMU_DEM_SHIFT); |
| } else { |
| avg_con |= ((avg_con & EXYNOS_TMU_AVG_MODE_MASK) | EXYNOS_TMU_AVG_MODE_4); |
| avg_con |= EXYNOS_TMU_DEM_ENABLE << EXYNOS_TMU_DEM_SHIFT; |
| } |
| |
| if (data->id == 0 || data->id == 1) |
| global_avg_con = avg_con; |
| |
| /* Set MUX_ADDR SFR according to sensor_type */ |
| switch (data->pdata->sensor_type) { |
| case TEM1456X : |
| counter_value0 = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE0); |
| counter_value0 &= ~(EXYNOS_TMU_EN_TEMP_SEN_OFF_MASK << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT); |
| counter_value0 |= EXYNOS_TMU_TEM1456X_SENSE_VALUE << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT; |
| writel(counter_value0, data->base + EXYNOS_TMU_REG_COUNTER_VALUE0); |
| |
| counter_value1 = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE1); |
| counter_value1 &= ~(EXYNOS_TMU_CLK_SENSE_ON_MASK << EXYNOS_TMU_CLK_SENSE_ON_SHIFT); |
| counter_value1 |= EXYNOS_TMU_TEM1456X_SENSE_VALUE << EXYNOS_TMU_CLK_SENSE_ON_SHIFT; |
| writel(counter_value1, data->base + EXYNOS_TMU_REG_COUNTER_VALUE1); |
| case TEM1455X : |
| mux_val = (data->pdata->sensor_type << EXYNOS_TMU_MUX_ADDR_SHIFT); |
| con |= (con & ~(EXYNOS_TMU_MUX_ADDR_MASK << EXYNOS_TMU_MUX_ADDR_SHIFT)) | mux_val; |
| break; |
| } |
| |
| if (on) { |
| con |= (t_buf_vref_sel << EXYNOS_TMU_REF_VOLTAGE_SHIFT); |
| con |= (t_buf_slope_sel << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); |
| con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| |
| for (i = (of_thermal_get_ntrips(tz) - 1); i >= 0; i--) { |
| |
| tz->ops->get_trip_type(tz, i, &type); |
| |
| if (type == THERMAL_TRIP_PASSIVE) |
| continue; |
| |
| bit_off = EXYNOS_TMU_INTEN_RISE7_SHIFT - interrupt_count; |
| |
| interrupt_en |= of_thermal_is_trip_valid(tz, i) << bit_off; |
| interrupt_count++; |
| } |
| |
| interrupt_en |= |
| interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; |
| } else { |
| con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con &= ~(1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| interrupt_en = 0; /* Disable all interrupts */ |
| } |
| |
| for (i = 0; i < TOTAL_SENSORS; i++) { |
| if (data->sensors & (1 << i)) { |
| int int_offset; |
| |
| if (i < 5) |
| int_offset = EXYNOS_TMU_REG_INTEN0 + EXYNOS_TMU_REG_INTEN_OFFSET * i; |
| else |
| int_offset = EXYNOS_TMU_REG_INTEN5 + EXYNOS_TMU_REG_INTEN_OFFSET * (i - 5) ; |
| |
| writel(interrupt_en, data->base + int_offset); |
| } |
| } |
| writel(con, data->base + EXYNOS_TMU_REG_CONTROL); |
| |
| /* Only TEM1002X sensor needs AVG_CON setting */ |
| if (data->pdata->sensor_type == TEM1002X) |
| writel(global_avg_con, data->base + EXYNOS_TMU_REG_AVG_CON); |
| |
| } |
| |
| static int exynos78XX_tmu_initialize(struct platform_device *pdev) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct thermal_zone_device *tz = data->tzd; |
| struct exynos_tmu_platform_data *pdata = data->pdata; |
| unsigned int rising_threshold = 0, falling_threshold = 0; |
| int temp, temp_hist; |
| unsigned int trim_info; |
| unsigned int reg_off = 0, bit_off; |
| int threshold_code, i; |
| int sensor; |
| int count = 0, interrupt_count = 0; |
| struct sensor_info temp_info; |
| enum thermal_trip_type type; |
| |
| u16 cal_type; |
| u32 temp_error1; |
| u32 temp_error2; |
| |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); |
| cal_type = (trim_info >> EXYNOS_TMU_CALIB_SEL_SHIFT) & EXYNOS_TMU_CALIB_SEL_MASK; |
| |
| for (sensor = 0; sensor < TOTAL_SENSORS; sensor++) { |
| /* Read the sensor error value from TRIMINFOX */ |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO + 0x4 * sensor); |
| temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; |
| temp_error2 = (trim_info >> EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) & EXYNOS_TMU_TEMP_MASK; |
| |
| /* If the sensor is used, save its information. */ |
| if (data->sensors & (1 << sensor)) { |
| /* Save sensor id */ |
| data->sensor_info[count].sensor_num = sensor; |
| dev_info(&pdev->dev, "Sensor number = %d\n", sensor); |
| |
| /* Check thermal calibration type */ |
| data->sensor_info[count].cal_type = cal_type; |
| |
| /* Check temp_error1 value */ |
| data->sensor_info[count].temp_error1 = temp_error1; |
| if (!data->sensor_info[count].temp_error1) |
| data->sensor_info[count].temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; |
| |
| /* Check temp_error2 if calibration type is TYPE_TWO_POINT_TRIMMING */ |
| if (data->sensor_info[count].cal_type == TYPE_TWO_POINT_TRIMMING) { |
| data->sensor_info[count].temp_error2 = temp_error2; |
| |
| if (!data->sensor_info[count].temp_error2) |
| data->sensor_info[count].temp_error2 = |
| ((pdata->efuse_value + 60) >> EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) |
| & EXYNOS_TMU_TEMP_MASK; |
| } |
| } |
| |
| temp_info.cal_type = cal_type; |
| temp_info.temp_error1 = temp_error1; |
| temp_info.temp_error2 = temp_error2; |
| |
| if (data->sensors & (1 << sensor)) { |
| interrupt_count = 0; |
| /* Write temperature code for rising and falling threshold */ |
| for (i = (of_thermal_get_ntrips(tz) - 1); i >= 0; i--) { |
| /* |
| * On exynos8 there are 4 rising and 4 falling threshold |
| * registers (0x50-0x5c and 0x60-0x6c respectively). Each |
| * register holds the value of two threshold levels (at bit |
| * offsets 0 and 16). Based on the fact that there are atmost |
| * eight possible trigger levels, calculate the register and |
| * bit offsets where the threshold levels are to be written. |
| * |
| * e.g. EXYNOS_THD_TEMP_RISE7_6 (0x50) |
| * [24:16] - Threshold level 7 |
| * [8:0] - Threshold level 6 |
| * e.g. EXYNOS_THD_TEMP_RISE5_4 (0x54) |
| * [24:16] - Threshold level 5 |
| * [8:0] - Threshold level 4 |
| * |
| * and similarly for falling thresholds. |
| * |
| * Based on the above, calculate the register and bit offsets |
| * for rising/falling threshold levels and populate them. |
| */ |
| |
| tz->ops->get_trip_type(tz, i, &type); |
| |
| if (type == THERMAL_TRIP_PASSIVE) |
| continue; |
| |
| reg_off = (interrupt_count / 2) * 4; |
| bit_off = ((interrupt_count + 1) % 2); |
| |
| if (sensor > 0) |
| reg_off = reg_off + EXYNOS_THD_TEMP_R_OFFSET + sensor * 0x20; |
| |
| tz->ops->get_trip_temp(tz, i, &temp); |
| temp /= MCELSIUS; |
| |
| tz->ops->get_trip_hyst(tz, i, &temp_hist); |
| temp_hist = temp - (temp_hist / MCELSIUS); |
| |
| /* Set 9-bit temperature code for rising threshold levels */ |
| threshold_code = temp_to_code_with_sensorinfo(data, temp, &data->sensor_info[count]); |
| rising_threshold = readl(data->base + |
| EXYNOS_THD_TEMP_RISE7_6 + reg_off); |
| rising_threshold &= ~(EXYNOS_TMU_TEMP_MASK << (16 * bit_off)); |
| rising_threshold |= threshold_code << (16 * bit_off); |
| writel(rising_threshold, |
| data->base + EXYNOS_THD_TEMP_RISE7_6 + reg_off); |
| |
| /* Set 9-bit temperature code for falling threshold levels */ |
| threshold_code = temp_to_code_with_sensorinfo(data, temp_hist, &data->sensor_info[count]); |
| |
| falling_threshold &= ~(EXYNOS_TMU_TEMP_MASK << (16 * bit_off)); |
| falling_threshold |= threshold_code << (16 * bit_off); |
| writel(falling_threshold, |
| data->base + EXYNOS_THD_TEMP_FALL7_6 + reg_off); |
| |
| interrupt_count++; |
| } |
| count++; |
| } |
| } |
| |
| data->tmu_clear_irqs(data); |
| |
| return 0; |
| } |
| |
| static void exynos78XX_tmu_control(struct platform_device *pdev, bool on) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct thermal_zone_device *tz = data->tzd; |
| unsigned int con, con1, interrupt_en, trim_info, trim_info1; |
| unsigned int ptat_cont, buf_cont; |
| unsigned int bit_off; |
| int i, interrupt_count = 0; |
| u32 mux_val; |
| u32 counter_value0, counter_value1; |
| enum thermal_trip_type type; |
| |
| interrupt_en = 0; |
| con = readl(data->base + EXYNOS_TMU_REG_CONTROL); |
| con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con &= ~(1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| writel(con, data->base + EXYNOS_TMU_REG_CONTROL); |
| |
| trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); |
| trim_info1 = readl(data->base + EXYNOS_TMU_REG_TRIMINFO1); |
| |
| /* Save PTAT_CONT, BUF_CONT value from TRIMINFO and TRIMINFO1 register */ |
| ptat_cont = (trim_info >> EXYNOS_TMU_T_BUF_VREF_SEL_SHIFT) |
| & (EXYNOS_TMU_T_PTAT_CONT_MASK); |
| buf_cont = (trim_info1 >> EXYNOS_TMU_T_BUF_SLOPE_SEL_SHIFT) |
| & (EXYNOS_TMU_T_BUF_CONT_MASK); |
| |
| |
| con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL)); |
| con1 = readl(data->base + EXYNOS_TMU_REG_CONTROL1); |
| |
| /* Set MUX_ADDR SFR according to sensor_type */ |
| switch (data->pdata->sensor_type) { |
| case TEM1456X : |
| counter_value0 = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE0); |
| counter_value0 &= ~(EXYNOS_TMU_EN_TEMP_SEN_OFF_MASK << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT); |
| counter_value0 |= EXYNOS_TMU_TEM1456X_SENSE_VALUE << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT; |
| writel(counter_value0, data->base + EXYNOS_TMU_REG_COUNTER_VALUE0); |
| |
| counter_value1 = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE1); |
| counter_value1 &= ~(EXYNOS_TMU_CLK_SENSE_ON_MASK << EXYNOS_TMU_CLK_SENSE_ON_SHIFT); |
| counter_value1 |= EXYNOS_TMU_TEM1456X_SENSE_VALUE << EXYNOS_TMU_CLK_SENSE_ON_SHIFT; |
| writel(counter_value1, data->base + EXYNOS_TMU_REG_COUNTER_VALUE1); |
| break; |
| case TEM1455X : |
| mux_val = (data->pdata->sensor_type << EXYNOS_TMU_MUX_ADDR_SHIFT); |
| con &= ~(EXYNOS_TMU_MUX_ADDR_MASK << EXYNOS_TMU_MUX_ADDR_SHIFT); |
| con |= (data->pdata->sensor_type << EXYNOS_TMU_MUX_ADDR_SHIFT); |
| break; |
| } |
| |
| if (on) { |
| con1 &= ~(EXYNOS_TMU_PTAT_CON_MASK << EXYNOS_TMU_PTAT_CON_SHIFT); |
| con1 &= ~(EXYNOS_TMU_BUF_CONT_MASK << EXYNOS_TMU_BUF_CONT_SHIFT); |
| con1 |= (ptat_cont << EXYNOS_TMU_PTAT_CON_SHIFT); |
| con1 |= (buf_cont << EXYNOS_TMU_BUF_CONT_SHIFT); |
| |
| con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| |
| for (i = (of_thermal_get_ntrips(tz) - 1); i >= 0; i--) { |
| |
| tz->ops->get_trip_type(tz, i, &type); |
| |
| if (type == THERMAL_TRIP_PASSIVE) |
| continue; |
| |
| bit_off = EXYNOS_TMU_INTEN_RISE7_SHIFT - interrupt_count; |
| |
| interrupt_en |= of_thermal_is_trip_valid(tz, i) << bit_off; |
| interrupt_count++; |
| } |
| |
| interrupt_en |= interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; |
| } else { |
| con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); |
| con &= ~(1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); |
| interrupt_en = 0; /* Disable all interrupts */ |
| } |
| |
| writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN0); |
| writel(con, data->base + EXYNOS_TMU_REG_CONTROL); |
| writel(con1, data->base + EXYNOS_TMU_REG_CONTROL1); |
| } |
| |
| #define MCINFO_LOG_THRESHOLD (4) |
| |
| static int exynos_get_temp(void *p, int *temp) |
| { |
| struct exynos_tmu_data *data = p; |
| struct thermal_cooling_device *cdev; |
| #ifdef CONFIG_EXYNOS_MCINFO |
| unsigned int mcinfo_count; |
| unsigned int mcinfo_result[4] = {0, 0, 0, 0}; |
| unsigned int mcinfo_logging = 0; |
| unsigned int mcinfo_temp = 0; |
| unsigned int i; |
| #endif |
| |
| if (!data || !data->tmu_read) |
| return -EINVAL; |
| |
| mutex_lock(&data->lock); |
| |
| if (data->num_of_sensors) |
| *temp = data->tmu_read(data) * MCELSIUS; |
| else |
| *temp = code_to_temp(data, data->tmu_read(data)) * MCELSIUS; |
| |
| mutex_unlock(&data->lock); |
| |
| cdev = data->cool_dev; |
| |
| if (!cdev) |
| return 0; |
| |
| mutex_lock(&thermal_suspend_lock); |
| |
| if (cdev->ops->set_cur_temp && data->id != 1) |
| cdev->ops->set_cur_temp(cdev, suspended, *temp / 1000); |
| |
| mutex_unlock(&thermal_suspend_lock); |
| |
| exynos_ss_thermal(data->pdata, *temp / 1000, data->tmu_name, 0); |
| #ifdef CONFIG_EXYNOS_MCINFO |
| if (data->id == 0) { |
| mcinfo_count = get_mcinfo_base_count(); |
| get_refresh_rate(mcinfo_result); |
| |
| for (i = 0; i < mcinfo_count; i++) { |
| mcinfo_temp |= (mcinfo_result[i] & 0xf) << (8 * i); |
| |
| if (mcinfo_result[i] >= MCINFO_LOG_THRESHOLD) |
| mcinfo_logging = 1; |
| } |
| |
| if (mcinfo_logging == 1) |
| exynos_ss_thermal(NULL, mcinfo_temp, "MCINFO", 0); |
| } |
| #endif |
| return 0; |
| } |
| |
| #ifdef CONFIG_SEC_BOOTSTAT |
| void sec_bootstat_get_thermal(int *temp) |
| { |
| struct exynos_tmu_data *data; |
| |
| list_for_each_entry(data, &dtm_dev_list, node) { |
| if (!strncasecmp(data->tmu_name, "MNGS", THERMAL_NAME_LENGTH)) { |
| exynos_get_temp(data, &temp[0]); |
| temp[0] /= 1000; |
| } |
| else if (!strncasecmp(data->tmu_name, "APOLLO", THERMAL_NAME_LENGTH)) { |
| exynos_get_temp(data, &temp[1]); |
| temp[1] /= 1000; |
| } |
| else if (!strncasecmp(data->tmu_name, "GPU", THERMAL_NAME_LENGTH)) { |
| exynos_get_temp(data, &temp[2]); |
| temp[2] /= 1000; |
| } |
| else if (!strncasecmp(data->tmu_name, "ISP", THERMAL_NAME_LENGTH)) { |
| exynos_get_temp(data, &temp[3]); |
| temp[3] /= 1000; |
| } |
| else |
| continue; |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_SEC_PM |
| |
| #define NR_THERMAL_SENSOR_MAX 10 |
| |
| static ssize_t |
| exynos_tmu_curr_temp(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct exynos_tmu_data *data; |
| int temp[NR_THERMAL_SENSOR_MAX] = {0, }; |
| int i, idx = 0; |
| ssize_t ret = 0; |
| |
| list_for_each_entry(data, &dtm_dev_list, node) { |
| if (data->id < NR_THERMAL_SENSOR_MAX) { |
| exynos_get_temp(data, &temp[idx]); |
| temp[idx++] /= 1000; |
| } else { |
| pr_err("%s: id:%d %s\n", __func__, data->id, |
| data->tmu_name); |
| continue; |
| } |
| } |
| |
| for (i = 0; i < idx; i++) |
| ret += sprintf(buf + ret, "%d,", temp[i]); |
| |
| sprintf(buf + ret - 1, "\n"); |
| |
| return ret; |
| } |
| |
| static DEVICE_ATTR(curr_temp, S_IRUGO, exynos_tmu_curr_temp, NULL); |
| |
| static struct attribute *exynos_tmu_sec_pm_attributes[] = { |
| &dev_attr_curr_temp.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group exynos_tmu_sec_pm_attr_grp = { |
| .attrs = exynos_tmu_sec_pm_attributes, |
| }; |
| |
| static int __init exynos_tmu_sec_pm_init(void) |
| { |
| int ret = 0; |
| struct device* dev; |
| |
| dev = sec_device_create(NULL, "exynos_tmu"); |
| |
| if (IS_ERR(dev)) { |
| pr_err("%s: failed to create device\n", __func__); |
| return PTR_ERR(dev); |
| } |
| |
| ret = sysfs_create_group(&dev->kobj, &exynos_tmu_sec_pm_attr_grp); |
| if (ret) { |
| pr_err("%s: failed to create sysfs group(%d)\n", __func__, ret); |
| goto err_create_sysfs; |
| } |
| |
| return ret; |
| |
| err_create_sysfs: |
| sec_device_destroy(dev->devt); |
| |
| return ret; |
| } |
| |
| late_initcall(exynos_tmu_sec_pm_init); |
| #endif /* CONFIG_SEC_PM */ |
| |
| #ifdef CONFIG_THERMAL_EMULATION |
| static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val, |
| int temp) |
| { |
| if (temp) { |
| temp /= MCELSIUS; |
| val &= ~(EXYNOS_EMUL_DATA_MASK << |
| EXYNOS_EMUL_DATA_SHIFT); |
| val |= (temp_to_code(data, temp) << |
| EXYNOS_EMUL_DATA_SHIFT) | |
| EXYNOS_EMUL_ENABLE; |
| } else { |
| val &= ~EXYNOS_EMUL_ENABLE; |
| } |
| |
| return val; |
| } |
| |
| static void exynos8890_tmu_set_emulation(struct exynos_tmu_data *data, |
| int temp) |
| { |
| unsigned int val; |
| u32 emul_con; |
| |
| emul_con = EXYNOS_TMU_REG_EMUL_CON; |
| |
| val = readl(data->base + emul_con); |
| val = get_emul_con_reg(data, val, temp); |
| writel(val, data->base + emul_con); |
| } |
| |
| static void exynos8895_tmu_set_emulation(struct exynos_tmu_data *data, |
| int temp) |
| { |
| unsigned int val; |
| u32 emul_con; |
| |
| emul_con = EXYNOS_TMU_REG_EMUL_CON; |
| |
| val = readl(data->base + emul_con); |
| |
| if (temp) { |
| temp /= MCELSIUS; |
| val &= ~(EXYNOS_EMUL_DATA_MASK << EXYNOS_EMUL_DATA_SHIFT); |
| val |= (temp_to_code_with_sensorinfo(data, temp, &data->sensor_info[0]) << EXYNOS_EMUL_DATA_SHIFT) |
| | EXYNOS_EMUL_ENABLE; |
| } else { |
| val &= ~EXYNOS_EMUL_ENABLE; |
| } |
| |
| writel(val, data->base + emul_con); |
| } |
| |
| static int exynos_tmu_set_emulation(void *drv_data, int temp) |
| { |
| struct exynos_tmu_data *data = drv_data; |
| int ret = -EINVAL; |
| |
| if (temp && temp < MCELSIUS) |
| goto out; |
| |
| mutex_lock(&data->lock); |
| data->tmu_set_emulation(data, temp); |
| mutex_unlock(&data->lock); |
| return 0; |
| out: |
| return ret; |
| } |
| #else |
| #define exynos8890_tmu_set_emulation NULL |
| #define exynos8895_tmu_set_emulation NULL |
| static int exynos_tmu_set_emulation(void *drv_data, int temp) |
| { return -EINVAL; } |
| #endif /* CONFIG_THERMAL_EMULATION */ |
| |
| static int exynos8890_tmu_read(struct exynos_tmu_data *data) |
| { |
| return readw(data->base + EXYNOS_TMU_REG_CURRENT_TEMP1_0) & |
| EXYNOS_TMU_TEMP_MASK; |
| } |
| |
| static int exynos8895_tmu_read(struct exynos_tmu_data *data) |
| { |
| u8 i; |
| u32 reg_offset, bit_offset; |
| u32 temp_code, temp_cel; |
| u32 count = 0, result = 0; |
| |
| for (i = 0; i < data->num_of_sensors; i++) { |
| if (data->sensor_info[i].sensor_num < 2) { |
| reg_offset = 0; |
| bit_offset = EXYNOS_TMU_TEMP_SHIFT * data->sensor_info[i].sensor_num; |
| } else { |
| reg_offset = ((data->sensor_info[i].sensor_num - 2) / 3 + 1) * 4; |
| bit_offset = EXYNOS_TMU_TEMP_SHIFT * ((data->sensor_info[i].sensor_num - 2) % 3); |
| } |
| |
| temp_code = (readl(data->base + EXYNOS_TMU_REG_CURRENT_TEMP1_0 + reg_offset) |
| >> bit_offset) & EXYNOS_TMU_TEMP_MASK; |
| temp_cel = code_to_temp_with_sensorinfo(data, temp_code, &data->sensor_info[i]); |
| |
| switch (data->sensing_mode) { |
| case AVG : result = result + temp_cel; |
| break; |
| case MAX : result = result > temp_cel ? result : temp_cel; |
| break; |
| case MIN : result = result < temp_cel ? result : temp_cel; |
| break; |
| case BALANCE: |
| if (data->sensor_info[i].sensor_num != 0) { |
| if (temp_cel >= data->balance_offset) |
| temp_cel = temp_cel - data->balance_offset; |
| else |
| temp_cel = 0; |
| } |
| result = result > temp_cel ? result : temp_cel; |
| break; |
| default : result = temp_cel; |
| break; |
| } |
| count++; |
| } |
| |
| switch (data->sensing_mode) { |
| case AVG : |
| if (count != 0) |
| result = result / count; |
| break; |
| case MAX : |
| case MIN : |
| case BALANCE: |
| default : |
| break; |
| } |
| |
| return result; |
| } |
| |
| static void exynos_tmu_work(struct work_struct *work) |
| { |
| struct exynos_tmu_data *data = container_of(work, |
| struct exynos_tmu_data, irq_work); |
| |
| exynos_report_trigger(data); |
| mutex_lock(&data->lock); |
| |
| /* TODO: take action based on particular interrupt */ |
| data->tmu_clear_irqs(data); |
| |
| mutex_unlock(&data->lock); |
| enable_irq(data->irq); |
| } |
| |
| static void exynos8890_tmu_clear_irqs(struct exynos_tmu_data *data) |
| { |
| unsigned int val_irq; |
| |
| val_irq = readl(data->base + EXYNOS_TMU_REG_INTPEND0); |
| writel(val_irq, data->base + EXYNOS_TMU_REG_INTPEND0); |
| } |
| |
| static void exynos8895_tmu_clear_irqs(struct exynos_tmu_data *data) |
| { |
| unsigned int i, val_irq; |
| u32 pend_reg; |
| |
| for (i = 0; i < TOTAL_SENSORS; i++) { |
| if (data->sensors & (1 << i)) { |
| if (i < 5) |
| pend_reg = EXYNOS_TMU_REG_INTPEND0 + EXYNOS_TMU_REG_INTPEN_OFFSET * i; |
| else |
| pend_reg = EXYNOS_TMU_REG_INTPEND5 + EXYNOS_TMU_REG_INTPEN_OFFSET * (i - 5); |
| |
| val_irq = readl(data->base + pend_reg); |
| writel(val_irq, data->base + pend_reg); |
| } |
| } |
| } |
| |
| static irqreturn_t exynos_tmu_irq(int irq, void *id) |
| { |
| struct exynos_tmu_data *data = id; |
| |
| disable_irq_nosync(irq); |
| schedule_work(&data->irq_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int exynos_pm_notifier(struct notifier_block *notifier, |
| unsigned long event, void *v) |
| { |
| struct exynos_tmu_data *devnode; |
| struct thermal_cooling_device *cdev; |
| |
| switch (event) { |
| case PM_SUSPEND_PREPARE: |
| mutex_lock(&thermal_suspend_lock); |
| suspended = true; |
| list_for_each_entry(devnode, &dtm_dev_list, node) { |
| cdev = devnode->cool_dev; |
| |
| if (cdev && cdev->ops->set_cur_temp && devnode->id != 1) |
| cdev->ops->set_cur_temp(cdev, suspended, 0); |
| } |
| mutex_unlock(&thermal_suspend_lock); |
| break; |
| case PM_POST_SUSPEND: |
| mutex_lock(&thermal_suspend_lock); |
| suspended = false; |
| list_for_each_entry(devnode, &dtm_dev_list, node) { |
| cdev = devnode->cool_dev; |
| |
| if (cdev && cdev->ops->set_cur_temp && devnode->id != 1) |
| cdev->ops->set_cur_temp(cdev, suspended, devnode->tzd->temperature / 1000); |
| } |
| mutex_unlock(&thermal_suspend_lock); |
| break; |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block exynos_tmu_pm_notifier = { |
| .notifier_call = exynos_pm_notifier, |
| }; |
| |
| static const struct of_device_id exynos_tmu_match[] = { |
| { .compatible = "samsung,exynos7872-tmu", }, |
| { .compatible = "samsung,exynos7885-tmu", }, |
| { .compatible = "samsung,exynos8890-tmu", }, |
| { .compatible = "samsung,exynos8895-tmu", }, |
| { /* sentinel */ }, |
| }; |
| MODULE_DEVICE_TABLE(of, exynos_tmu_match); |
| |
| static int exynos_of_get_soc_type(struct device_node *np) |
| { |
| if (of_device_is_compatible(np, "samsung,exynos7872-tmu")) |
| return SOC_ARCH_EXYNOS7872; |
| if (of_device_is_compatible(np, "samsung,exynos7885-tmu")) |
| return SOC_ARCH_EXYNOS7885; |
| if (of_device_is_compatible(np, "samsung,exynos8890-tmu")) |
| return SOC_ARCH_EXYNOS8890; |
| if (of_device_is_compatible(np, "samsung,exynos8895-tmu")) |
| return SOC_ARCH_EXYNOS8895; |
| |
| return -EINVAL; |
| } |
| |
| static int exynos_of_sensor_conf(struct device_node *np, |
| struct exynos_tmu_platform_data *pdata) |
| { |
| u32 value; |
| int ret; |
| |
| of_node_get(np); |
| |
| ret = of_property_read_u32(np, "samsung,tmu_gain", &value); |
| pdata->gain = (u8)value; |
| of_property_read_u32(np, "samsung,tmu_reference_voltage", &value); |
| pdata->reference_voltage = (u8)value; |
| of_property_read_u32(np, "samsung,tmu_noise_cancel_mode", &value); |
| pdata->noise_cancel_mode = (u8)value; |
| |
| of_property_read_u32(np, "samsung,tmu_efuse_value", |
| &pdata->efuse_value); |
| |
| of_property_read_u32(np, "samsung,tmu_first_point_trim", &value); |
| pdata->first_point_trim = (u8)value; |
| of_property_read_u32(np, "samsung,tmu_second_point_trim", &value); |
| pdata->second_point_trim = (u8)value; |
| of_property_read_u32(np, "samsung,tmu_default_temp_offset", &value); |
| pdata->default_temp_offset = (u8)value; |
| |
| of_property_read_u32(np, "samsung,tmu_default_trip_temp", &pdata->trip_temp); |
| of_property_read_u32(np, "samsung,tmu_cal_type", &pdata->cal_type); |
| |
| if (of_property_read_u32(np, "samsung,tmu_sensor_type", &pdata->sensor_type)) |
| pr_err("%s: failed to get thermel sensor type\n", __func__); |
| |
| of_node_put(np); |
| return 0; |
| } |
| |
| static int exynos_map_dt_data(struct platform_device *pdev) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct exynos_tmu_platform_data *pdata; |
| struct resource res; |
| int i; |
| const char *temp, *tmu_name; |
| |
| if (!data || !pdev->dev.of_node) |
| return -ENODEV; |
| |
| data->np = pdev->dev.of_node; |
| |
| if (of_property_read_u32(pdev->dev.of_node, "id", &data->id)) { |
| dev_err(&pdev->dev, "failed to get TMU ID\n"); |
| return -ENODEV; |
| } |
| |
| data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); |
| if (data->irq <= 0) { |
| dev_err(&pdev->dev, "failed to get IRQ\n"); |
| return -ENODEV; |
| } |
| |
| if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { |
| dev_err(&pdev->dev, "failed to get Resource 0\n"); |
| return -ENODEV; |
| } |
| |
| data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); |
| if (!data->base) { |
| dev_err(&pdev->dev, "Failed to ioremap memory\n"); |
| return -EADDRNOTAVAIL; |
| } |
| |
| /* If remote sensor is exist, parse it. Remote sensor is used when reading the temperature. */ |
| if (!of_property_read_u32(pdev->dev.of_node, "sensors", &data->sensors)) { |
| for (i = 0; i < TOTAL_SENSORS; i++) |
| if (data->sensors & (1 << i)) |
| data->num_of_sensors++; |
| |
| data->sensor_info = kzalloc(sizeof(struct sensor_info) * data->num_of_sensors, GFP_KERNEL); |
| } else { |
| dev_err(&pdev->dev, "failed to get sensors information \n"); |
| return -ENODEV; |
| } |
| |
| if (of_property_read_string(pdev->dev.of_node, "tmu_name", &tmu_name)) { |
| dev_err(&pdev->dev, "failed to get tmu_name\n"); |
| } else |
| strncpy(data->tmu_name, tmu_name, THERMAL_NAME_LENGTH); |
| |
| if (of_property_read_string(pdev->dev.of_node, "sensing_mode", &temp)) |
| dev_err(&pdev->dev, "failed to get sensing_mode of thermel sensor\n"); |
| else { |
| for (i = 0; i<ARRAY_SIZE(sensing_method); i++) |
| if (!strcasecmp(temp, sensing_method[i])) |
| data->sensing_mode = i; |
| } |
| |
| data->balance_offset = DEFAULT_BALANCE_OFFSET; |
| |
| data->hotplug_enable = of_property_read_bool(pdev->dev.of_node, "hotplug_enable"); |
| if (data->hotplug_enable) { |
| dev_info(&pdev->dev, "thermal zone use hotplug function \n"); |
| of_property_read_u32(pdev->dev.of_node, "hotplug_in_threshold", |
| &data->hotplug_in_threshold); |
| if (!data->hotplug_in_threshold) |
| dev_err(&pdev->dev, "No input hotplug_in_threshold \n"); |
| |
| of_property_read_u32(pdev->dev.of_node, "hotplug_out_threshold", |
| &data->hotplug_out_threshold); |
| if (!data->hotplug_out_threshold) |
| dev_err(&pdev->dev, "No input hotplug_out_threshold \n"); |
| } |
| |
| pdata = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_platform_data), GFP_KERNEL); |
| if (!pdata) |
| return -ENOMEM; |
| |
| exynos_of_sensor_conf(pdev->dev.of_node, pdata); |
| data->pdata = pdata; |
| data->soc = exynos_of_get_soc_type(pdev->dev.of_node); |
| |
| switch (data->soc) { |
| case SOC_ARCH_EXYNOS8890: |
| data->tmu_initialize = exynos8890_tmu_initialize; |
| data->tmu_control = exynos8890_tmu_control; |
| data->tmu_read = exynos8890_tmu_read; |
| data->tmu_set_emulation = exynos8890_tmu_set_emulation; |
| data->tmu_clear_irqs = exynos8890_tmu_clear_irqs; |
| break; |
| case SOC_ARCH_EXYNOS8895: |
| data->tmu_initialize = exynos8895_tmu_initialize; |
| data->tmu_control = exynos8895_tmu_control; |
| data->tmu_read = exynos8895_tmu_read; |
| data->tmu_set_emulation = exynos8895_tmu_set_emulation; |
| data->tmu_clear_irqs = exynos8895_tmu_clear_irqs; |
| break; |
| case SOC_ARCH_EXYNOS7872: |
| case SOC_ARCH_EXYNOS7885: |
| data->tmu_initialize = exynos78XX_tmu_initialize; |
| data->tmu_control = exynos78XX_tmu_control; |
| data->tmu_read = exynos8895_tmu_read; |
| data->tmu_set_emulation = exynos8895_tmu_set_emulation; |
| data->tmu_clear_irqs = exynos8895_tmu_clear_irqs; |
| break; |
| default: |
| dev_err(&pdev->dev, "Platform not supported\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_SEC_DEBUG_HW_PARAM |
| static u64 last_time, curr_time; |
| extern struct thermal_data_devices thermal_data_info[THERMAL_ZONE_MAX]; |
| #endif |
| |
| struct pm_qos_request thermal_cpu_hotplug_request; |
| static int exynos_throttle_cpu_hotplug(void *p, int temp) |
| { |
| struct exynos_tmu_data *data = p; |
| int ret = 0; |
| |
| temp = temp / MCELSIUS; |
| |
| if (is_cpu_hotplugged_out) { |
| if (temp < data->hotplug_in_threshold) { |
| /* |
| * If current temperature is lower than low threshold, |
| * call cluster1_cores_hotplug(false) for hotplugged out cpus. |
| */ |
| pm_qos_update_request(&thermal_cpu_hotplug_request, |
| NR_CPUS); |
| is_cpu_hotplugged_out = false; |
| #ifdef CONFIG_SEC_DEBUG_HW_PARAM |
| curr_time = ktime_to_ns(ktime_get()) / 1000000; |
| if(last_time) { |
| thermal_data_info[THERMAL_ZONE_MNGS].hotplug_out += |
| curr_time - last_time; |
| } |
| #endif |
| } |
| } else { |
| if (temp >= data->hotplug_out_threshold) { |
| /* |
| * If current temperature is higher than high threshold, |
| * call cluster1_cores_hotplug(true) to hold temperature down. |
| */ |
| is_cpu_hotplugged_out = true; |
| |
| pm_qos_update_request(&thermal_cpu_hotplug_request, |
| NR_HOTPLUG_CPUS); |
| #ifdef CONFIG_SEC_DEBUG_HW_PARAM |
| last_time = ktime_to_ns(ktime_get()) / 1000000; |
| #endif |
| } |
| } |
| |
| return ret; |
| } |
| |
| static struct thermal_zone_of_device_ops exynos_hotplug_sensor_ops = { |
| .get_temp = exynos_get_temp, |
| .set_emul_temp = exynos_tmu_set_emulation, |
| .throttle_cpu_hotplug = exynos_throttle_cpu_hotplug, |
| }; |
| |
| static struct thermal_zone_of_device_ops exynos_sensor_ops = { |
| .get_temp = exynos_get_temp, |
| .set_emul_temp = exynos_tmu_set_emulation, |
| }; |
| |
| static int exynos_cpufreq_cooling_register(struct exynos_tmu_data *data) |
| { |
| struct device_node *np, *child = NULL, *gchild, *ggchild; |
| struct device_node *cool_np; |
| struct of_phandle_args cooling_spec; |
| struct cpumask mask_val; |
| int cpu, ret; |
| const char *governor_name; |
| u32 power_coefficient = 0; |
| void *gen_block; |
| struct ect_gen_param_table *pwr_coeff; |
| |
| np = of_find_node_by_name(NULL, "thermal-zones"); |
| if (!np) |
| return -ENODEV; |
| |
| /* Register cpufreq cooling device */ |
| for_each_child_of_node(np, child) { |
| struct device_node *zone_np; |
| zone_np = of_parse_phandle(child, "thermal-sensors", 0); |
| |
| if (zone_np == data->np) break; |
| } |
| |
| gchild = of_get_child_by_name(child, "cooling-maps"); |
| ggchild = of_get_next_child(gchild, NULL); |
| ret = of_parse_phandle_with_args(ggchild, "cooling-device", "#cooling-cells", |
| 0, &cooling_spec); |
| if (ret < 0) |
| pr_err("%s do not get cooling spec(err = %d) \n", data->tmu_name, ret); |
| |
| cool_np = cooling_spec.np; |
| |
| for_each_possible_cpu(cpu) { |
| if (cpu < NR_CPUS && cpu_topology[cpu].cluster_id == data->id) { |
| cpumask_copy(&mask_val, topology_core_cpumask(cpu)); |
| cpumask_and(&mask_val, &mask_val, cpu_online_mask); |
| } |
| } |
| |
| if (!of_property_read_string(child, "governor", &governor_name)) { |
| if (!strncasecmp(governor_name, "power_allocator", THERMAL_NAME_LENGTH)) { |
| gen_block = ect_get_block("GEN"); |
| if (gen_block == NULL) { |
| pr_err("%s: Failed to get gen block from ECT\n", __func__); |
| return -EINVAL; |
| } |
| pwr_coeff = ect_gen_param_get_table(gen_block, "DTM_PWR_Coeff"); |
| if (pwr_coeff == NULL) { |
| pr_err("%s: Failed to get power coeff from ECT\n", __func__); |
| return -EINVAL; |
| } |
| power_coefficient = pwr_coeff->parameter[data->id]; |
| } |
| } |
| |
| data->cool_dev = of_cpufreq_power_cooling_register(cool_np, &mask_val, power_coefficient, NULL); |
| |
| if (IS_ERR(data->cool_dev)) { |
| data->cool_dev = NULL; |
| pr_err("cooling device register fail (mask = %x) \n", *(unsigned int*)cpumask_bits(&mask_val)); |
| return -ENODEV; |
| } |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_GPU_THERMAL |
| |
| #ifdef CONFIG_MALI_DEBUG_KERNEL_SYSFS |
| struct exynos_tmu_data *gpu_thermal_data = NULL; |
| #endif |
| |
| static int exynos_gpufreq_cooling_register(struct exynos_tmu_data *data) |
| { |
| struct device_node *np, *child = NULL, *gchild, *ggchild; |
| struct device_node *cool_np; |
| struct of_phandle_args cooling_spec; |
| int ret; |
| const char *governor_name; |
| u32 power_coefficient = 0; |
| void *gen_block; |
| struct ect_gen_param_table *pwr_coeff; |
| |
| np = of_find_node_by_name(NULL, "thermal-zones"); |
| if (!np) |
| return -ENODEV; |
| |
| /* Regist gpufreq cooling device */ |
| for_each_child_of_node(np, child) { |
| struct device_node *zone_np; |
| zone_np = of_parse_phandle(child, "thermal-sensors", 0); |
| |
| if (zone_np == data->np) break; |
| } |
| |
| gchild = of_get_child_by_name(child, "cooling-maps"); |
| ggchild = of_get_next_child(gchild, NULL); |
| ret = of_parse_phandle_with_args(ggchild, "cooling-device", "#cooling-cells", |
| 0, &cooling_spec); |
| if (ret < 0) |
| pr_err("%s do not get cooling spec(err = %d) \n", data->tmu_name, ret); |
| |
| cool_np = cooling_spec.np; |
| |
| if (!of_property_read_string(child, "governor", &governor_name)) { |
| if (!strncasecmp(governor_name, "power_allocator", THERMAL_NAME_LENGTH)) { |
| gen_block = ect_get_block("GEN"); |
| if (gen_block == NULL) { |
| pr_err("%s: Failed to get gen block from ECT\n", __func__); |
| return -EINVAL; |
| } |
| pwr_coeff = ect_gen_param_get_table(gen_block, "DTM_PWR_Coeff"); |
| if (pwr_coeff == NULL) { |
| pr_err("%s: Failed to get power coeff from ECT\n", __func__); |
| return -EINVAL; |
| } |
| power_coefficient = pwr_coeff->parameter[data->id]; |
| } |
| } |
| |
| data->cool_dev = of_gpufreq_power_cooling_register(cool_np, NULL, power_coefficient, NULL); |
| |
| if (IS_ERR(data->cool_dev)) { |
| data->cool_dev = NULL; |
| pr_err("gpu cooling device register fail \n"); |
| return -ENODEV; |
| } |
| |
| #ifdef CONFIG_MALI_DEBUG_KERNEL_SYSFS |
| gpu_thermal_data = data; |
| #endif |
| |
| return ret; |
| } |
| #else |
| static int exynos_gpufreq_cooling_register(struct exynos_tmu_data *data) {return 0;} |
| #endif |
| |
| #ifdef CONFIG_ISP_THERMAL |
| static int exynos_isp_cooling_register(struct exynos_tmu_data *data) |
| { |
| struct device_node *np, *child = NULL, *gchild, *ggchild; |
| struct device_node *cool_np; |
| struct of_phandle_args cooling_spec; |
| int ret; |
| |
| np = of_find_node_by_name(NULL, "thermal-zones"); |
| if (!np) |
| return -ENODEV; |
| |
| /* Regist isp cooling device */ |
| for_each_child_of_node(np, child) { |
| struct device_node *zone_np; |
| zone_np = of_parse_phandle(child, "thermal-sensors", 0); |
| |
| if (zone_np == data->np) break; |
| } |
| |
| gchild = of_get_child_by_name(child, "cooling-maps"); |
| ggchild = of_get_next_child(gchild, NULL); |
| ret = of_parse_phandle_with_args(ggchild, "cooling-device", "#cooling-cells", |
| 0, &cooling_spec); |
| if (ret < 0) |
| pr_err("%s do not get cooling spec(err = %d) \n", data->tmu_name, ret); |
| |
| cool_np = cooling_spec.np; |
| |
| data->cool_dev = of_isp_cooling_register(cool_np, NULL); |
| |
| if (IS_ERR(data->cool_dev)) { |
| data->cool_dev = NULL; |
| pr_err("isp cooling device register fail \n"); |
| return -ENODEV; |
| } |
| |
| return ret; |
| } |
| #else |
| static int exynos_isp_cooling_register(struct exynos_tmu_data *data) {return 0;} |
| #endif |
| |
| #ifdef CONFIG_SCHED_HMP |
| extern struct cpumask hmp_fast_cpu_mask; |
| static int exynos_tmu_cpus_notifier(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| struct exynos_tmu_data *tmudata = container_of(nb, struct exynos_tmu_data, nb); |
| struct thermal_zone_device *tz = tmudata->tzd; |
| struct cpumask mask; |
| int big_cpu_cnt; |
| int count; |
| struct cpufreq_cooling_device *cpufreq_device = (struct cpufreq_cooling_device *)tmudata->cool_dev->devdata; |
| |
| |
| cpumask_copy(&mask, data); |
| cpumask_and(&mask, &mask, &hmp_fast_cpu_mask); |
| big_cpu_cnt = cpumask_weight(&mask); |
| |
| cpumask_copy(&cpufreq_device->target_cpus, &mask); |
| |
| switch (event) { |
| case CPUS_DOWN_COMPLETE: |
| if (big_cpu_cnt == DUAL_CPU) { |
| for (count = 0; count < tz->trips; count++) |
| thermal_notify_framework(tz, count); |
| } |
| break; |
| case CPUS_UP_PREPARE: |
| if (big_cpu_cnt == DUAL_CPU || big_cpu_cnt == QUAD_CPU) { |
| for (count = 0; count < tz->trips; count++) |
| thermal_notify_framework(tz, count); |
| } |
| break; |
| } |
| return NOTIFY_OK; |
| } |
| #endif |
| |
| static ssize_t |
| balance_offset_show(struct device *dev, struct device_attribute *devattr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", data->balance_offset); |
| } |
| |
| static ssize_t |
| balance_offset_store(struct device *dev, struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| int balance_offset = 0; |
| |
| mutex_lock(&data->lock); |
| |
| if (kstrtos32(buf, 10, &balance_offset)) { |
| mutex_unlock(&data->lock); |
| return -EINVAL; |
| } |
| |
| data->balance_offset = balance_offset; |
| |
| mutex_unlock(&data->lock); |
| |
| return count; |
| } |
| |
| static ssize_t |
| all_temp_show(struct device *dev, struct device_attribute *devattr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| int i, len = 0; |
| u32 reg_offset, bit_offset; |
| u32 temp_code, temp_cel; |
| |
| for (i = 0; i < data->num_of_sensors; i++) { |
| if (data->sensor_info[i].sensor_num < 2) { |
| reg_offset = 0; |
| bit_offset = EXYNOS_TMU_TEMP_SHIFT * data->sensor_info[i].sensor_num; |
| } else { |
| reg_offset = ((data->sensor_info[i].sensor_num - 2) / 3 + 1) * 4; |
| bit_offset = EXYNOS_TMU_TEMP_SHIFT * ((data->sensor_info[i].sensor_num - 2) % 3); |
| } |
| |
| temp_code = (readl(data->base + EXYNOS_TMU_REG_CURRENT_TEMP1_0 + reg_offset) |
| >> bit_offset) & EXYNOS_TMU_TEMP_MASK; |
| temp_cel = code_to_temp_with_sensorinfo(data, temp_code, &data->sensor_info[i]); |
| |
| len += snprintf(&buf[len], PAGE_SIZE, "%u, ", temp_cel); |
| } |
| |
| len += snprintf(&buf[len], PAGE_SIZE, "\n"); |
| |
| return len; |
| } |
| |
| static ssize_t |
| hotplug_out_temp_show(struct device *dev, struct device_attribute *devattr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", data->hotplug_out_threshold); |
| } |
| |
| static ssize_t |
| hotplug_out_temp_store(struct device *dev, struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| int hotplug_out = 0; |
| |
| mutex_lock(&data->lock); |
| |
| if (kstrtos32(buf, 10, &hotplug_out)) { |
| mutex_unlock(&data->lock); |
| return -EINVAL; |
| } |
| |
| data->hotplug_out_threshold = hotplug_out; |
| |
| mutex_unlock(&data->lock); |
| |
| return count; |
| } |
| |
| static ssize_t |
| hotplug_in_temp_show(struct device *dev, struct device_attribute *devattr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", data->hotplug_in_threshold); |
| } |
| |
| static ssize_t |
| hotplug_in_temp_store(struct device *dev, struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| int hotplug_in = 0; |
| |
| mutex_lock(&data->lock); |
| |
| if (kstrtos32(buf, 10, &hotplug_in)) { |
| mutex_unlock(&data->lock); |
| return -EINVAL; |
| } |
| |
| data->hotplug_in_threshold = hotplug_in; |
| |
| mutex_unlock(&data->lock); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(balance_offset, S_IWUSR | S_IRUGO, balance_offset_show, |
| balance_offset_store); |
| |
| static DEVICE_ATTR(all_temp, S_IRUGO, all_temp_show, NULL); |
| |
| static DEVICE_ATTR(hotplug_out_temp, S_IWUSR | S_IRUGO, hotplug_out_temp_show, |
| hotplug_out_temp_store); |
| |
| static DEVICE_ATTR(hotplug_in_temp, S_IWUSR | S_IRUGO, hotplug_in_temp_show, |
| hotplug_in_temp_store); |
| |
| static struct attribute *exynos_tmu_attrs[] = { |
| &dev_attr_balance_offset.attr, |
| &dev_attr_all_temp.attr, |
| &dev_attr_hotplug_out_temp.attr, |
| &dev_attr_hotplug_in_temp.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group exynos_tmu_attr_group = { |
| .attrs = exynos_tmu_attrs, |
| }; |
| |
| static int exynos_tmu_probe(struct platform_device *pdev) |
| { |
| struct exynos_tmu_data *data; |
| int ret; |
| #ifdef CONFIG_CPU_FREQ |
| if (!cpufreq_frequency_get_table(0)) |
| return -EPROBE_DEFER; |
| #endif |
| |
| data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), |
| GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| platform_set_drvdata(pdev, data); |
| mutex_init(&data->lock); |
| |
| ret = exynos_map_dt_data(pdev); |
| if (ret) |
| goto err_sensor; |
| |
| if (data->id == 0 || data->id == 1) { |
| ret = exynos_cpufreq_cooling_register(data); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed cooling register \n"); |
| goto err_sensor; |
| } |
| } else if (data->id == 2) { |
| ret = gpu_cooling_table_init(pdev); |
| if (ret) |
| goto err_sensor; |
| |
| ret = exynos_gpufreq_cooling_register(data); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed cooling register \n"); |
| goto err_sensor; |
| } |
| } else if (data->id == 3) { |
| ret = isp_cooling_table_init(pdev); |
| if (ret) |
| goto err_sensor; |
| |
| ret = exynos_isp_cooling_register(data); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed cooling register \n"); |
| goto err_sensor; |
| } |
| } |
| |
| INIT_WORK(&data->irq_work, exynos_tmu_work); |
| |
| /* |
| * data->tzd must be registered before calling exynos_tmu_initialize(), |
| * requesting irq and calling exynos_tmu_control(). |
| */ |
| if(data->hotplug_enable) |
| pm_qos_add_request(&thermal_cpu_hotplug_request, |
| PM_QOS_CPU_ONLINE_MAX, |
| PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE); |
| |
| data->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, data, |
| data->hotplug_enable ? |
| &exynos_hotplug_sensor_ops : |
| &exynos_sensor_ops); |
| if (IS_ERR(data->tzd)) { |
| ret = PTR_ERR(data->tzd); |
| dev_err(&pdev->dev, "Failed to register sensor: %d\n", ret); |
| goto err_sensor; |
| } |
| |
| data->num_probe = (readl(data->base + EXYNOS_TMU_REG_CONTROL1) >> EXYNOS_TMU_NUM_PROBE_SHIFT) |
| & EXYNOS_TMU_NUM_PROBE_MASK; |
| |
| ret = exynos_tmu_initialize(pdev); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed to initialize TMU\n"); |
| goto err_thermal; |
| } |
| |
| ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, |
| IRQF_SHARED, dev_name(&pdev->dev), data); |
| if (ret) { |
| dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); |
| goto err_thermal; |
| } |
| |
| exynos_tmu_control(pdev, true); |
| |
| ret = sysfs_create_group(&pdev->dev.kobj, &exynos_tmu_attr_group); |
| if (ret) |
| dev_err(&pdev->dev, "cannot create exynos tmu attr group"); |
| |
| mutex_lock(&data->lock); |
| list_add_tail(&data->node, &dtm_dev_list); |
| mutex_unlock(&data->lock); |
| |
| if (list_is_singular(&dtm_dev_list)) { |
| register_pm_notifier(&exynos_tmu_pm_notifier); |
| #ifdef CONFIG_SCHED_HMP |
| data->nb.notifier_call = exynos_tmu_cpus_notifier; |
| register_cpus_notifier(&data->nb); |
| #endif |
| } |
| |
| if (!IS_ERR(data->tzd)) |
| data->tzd->ops->set_mode(data->tzd, THERMAL_DEVICE_ENABLED); |
| |
| return 0; |
| |
| err_thermal: |
| thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd); |
| err_sensor: |
| return ret; |
| } |
| |
| static int exynos_tmu_remove(struct platform_device *pdev) |
| { |
| struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
| struct thermal_zone_device *tzd = data->tzd; |
| struct exynos_tmu_data *devnode; |
| |
| if (list_is_singular(&dtm_dev_list)) { |
| unregister_pm_notifier(&exynos_tmu_pm_notifier); |
| unregister_cpus_notifier(&data->nb); |
| } |
| |
| thermal_zone_of_sensor_unregister(&pdev->dev, tzd); |
| exynos_tmu_control(pdev, false); |
| |
| mutex_lock(&data->lock); |
| list_for_each_entry(devnode, &dtm_dev_list, node) { |
| if (devnode->id == data->id) { |
| list_del(&devnode->node); |
| } |
| } |
| mutex_unlock(&data->lock); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int exynos_tmu_suspend(struct device *dev) |
| { |
| exynos_tmu_control(to_platform_device(dev), false); |
| |
| return 0; |
| } |
| |
| static int exynos_tmu_resume(struct device *dev) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| exynos_tmu_initialize(pdev); |
| exynos_tmu_control(pdev, true); |
| |
| return 0; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, |
| exynos_tmu_suspend, exynos_tmu_resume); |
| #define EXYNOS_TMU_PM (&exynos_tmu_pm) |
| #else |
| #define EXYNOS_TMU_PM NULL |
| #endif |
| |
| static struct platform_driver exynos_tmu_driver = { |
| .driver = { |
| .name = "exynos-tmu", |
| .pm = EXYNOS_TMU_PM, |
| .of_match_table = exynos_tmu_match, |
| }, |
| .probe = exynos_tmu_probe, |
| .remove = exynos_tmu_remove, |
| }; |
| |
| module_platform_driver(exynos_tmu_driver); |
| |
| MODULE_DESCRIPTION("EXYNOS TMU Driver"); |
| MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:exynos-tmu"); |