2196 lines
63 KiB
C
Executable File
2196 lines
63 KiB
C
Executable File
/*
|
|
* 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/debugfs.h>
|
|
#include <linux/debug-snapshot.h>
|
|
#include <linux/cpuhotplug.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>
|
|
#ifdef CONFIG_SND_SOC_SAMSUNG_ABOX
|
|
#include <sound/samsung/abox.h>
|
|
#endif
|
|
|
|
#include "exynos_tmu.h"
|
|
#include "../thermal_core.h"
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
#include "exynos_acpm_tmu.h"
|
|
#include "soc/samsung/exynos-pmu.h"
|
|
#endif
|
|
#include <soc/samsung/exynos-cpuhp.h>
|
|
|
|
#ifdef CONFIG_SEC_PM
|
|
#include <linux/sec_class.h>
|
|
#endif
|
|
|
|
/* Exynos generic registers */
|
|
#define EXYNOS_TMU_REG_TRIMINFO7_0(p) (((p) - 0) * 4)
|
|
#define EXYNOS_TMU_REG_TRIMINFO15_8(p) (((p) - 8) * 4 + 0x400)
|
|
#define EXYNOS_TMU_REG_TRIMINFO(p) (((p) <= 7) ? \
|
|
EXYNOS_TMU_REG_TRIMINFO7_0(p) : \
|
|
EXYNOS_TMU_REG_TRIMINFO15_8(p))
|
|
#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_CURRENT_TEMP9_8 0x440
|
|
#define EXYNOS_TMU_REG_CURRENT_TEMP12_10 0x444
|
|
#define EXYNOS_TMU_REG_CURRENT_TEMP15_13 0x448
|
|
#define EXYNOS_TMU_REG_INTEN0 0x110
|
|
#define EXYNOS_TMU_REG_INTEN5 0x310
|
|
#define EXYNOS_TMU_REG_INTEN8 0x650
|
|
#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_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_TMU_REG_THD_TEMP0 0x50
|
|
#define EXYNOS_TMU_REG_THD_TEMP1 0x170
|
|
#define EXYNOS_TMU_REG_THD_TEMP8 0x450
|
|
#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_TRIM0 (0x3C)
|
|
#define EXYNOS_TMU_T_TRIM0_SHIFT (18)
|
|
#define EXYNOS_TMU_T_TRIM0_MASK (0xF)
|
|
#define EXYNOS_TMU_T_VREF_TRIM_SHIFT (14)
|
|
#define EXYNOS_TMU_T_VREF_TRIM_MASK (0xF)
|
|
#define EXYNOS_TMU_T_VBE_I_TRIM_SHIFT (10)
|
|
#define EXYNOS_TMU_T_VBE_I_TRIM_MASK (0xF)
|
|
#define EXYNOS_TMU_BGRI_TRIM_SHIFT (20)
|
|
#define EXYNOS_TMU_BGRI_TRIM_MASK (0xF)
|
|
#define EXYNOS_TMU_VREF_TRIM_SHIFT (12)
|
|
#define EXYNOS_TMU_VREF_TRIM_MASK (0xF)
|
|
#define EXYNOS_TMU_VBEI_TRIM_SHIFT (8)
|
|
#define EXYNOS_TMU_VBEI_TRIM_MASK (0xF)
|
|
|
|
#define EXYNOS_TMU_REG_INTPEND0 (0x118)
|
|
#define EXYNOS_TMU_REG_INTPEND5 (0x318)
|
|
#define EXYNOS_TMU_REG_INTPEND8 (0x658)
|
|
#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_TEM1051X_SENSE_VALUE (0x028A)
|
|
#define EXYNOS_TMU_TEM1002X_SENSE_VALUE (0x0514)
|
|
|
|
#define EXYNOS_TMU_NUM_PROBE_SHIFT (16)
|
|
#define EXYNOS_TMU_NUM_PROBE_MASK (0xf)
|
|
#define EXYNOS_TMU_LPI_MODE_SHIFT (10)
|
|
#define EXYNOS_TMU_LPI_MODE_MASK (1)
|
|
|
|
#define EXYNOS_GPU_THERMAL_ZONE_ID (2)
|
|
|
|
#define TOTAL_SENSORS 16
|
|
#define DEFAULT_BALANCE_OFFSET 20
|
|
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
#define PMUREG_AUD_STATUS 0x4004
|
|
static struct acpm_tmu_cap cap;
|
|
static unsigned int num_of_devices, suspended_count;
|
|
static bool cp_call_mode;
|
|
#ifdef CONFIG_SND_SOC_SAMSUNG_ABOX
|
|
#if defined(CONFIG_SOC_EXYNOS9810)
|
|
static bool is_aud_on(void)
|
|
{
|
|
unsigned int val;
|
|
|
|
exynos_pmu_read(PMUREG_AUD_STATUS, &val);
|
|
|
|
return ((val & 0xf) == 0xf);
|
|
}
|
|
#endif
|
|
#endif
|
|
#else
|
|
static bool suspended;
|
|
static DEFINE_MUTEX (thermal_suspend_lock);
|
|
#endif
|
|
static bool is_cpu_hotplugged_out;
|
|
|
|
/* Define thermal interrupt related workqueue */
|
|
struct workqueue_struct *thermal_irq_wq = NULL;
|
|
|
|
/* list of multiple instance for each thermal sensor */
|
|
static LIST_HEAD(dtm_dev_list);
|
|
|
|
static u32 t_bgri_trim;
|
|
static u32 t_vref_trim;
|
|
static u32 t_vbei_trim;
|
|
|
|
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, THERMAL_EVENT_UNSPECIFIED);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
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;
|
|
|
|
if (of_thermal_get_ntrips(data->tzd) > data->ntrip) {
|
|
dev_info(&pdev->dev,
|
|
"More trip points than supported by this TMU.\n");
|
|
dev_info(&pdev->dev,
|
|
"%d trip points should be configured in polling mode.\n",
|
|
(of_thermal_get_ntrips(data->tzd) - data->ntrip));
|
|
}
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = data->tmu_initialize(pdev);
|
|
mutex_unlock(&data->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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);
|
|
data->enabled = on;
|
|
mutex_unlock(&data->lock);
|
|
}
|
|
|
|
static int exynos9810_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 trim_info, temp_error1, temp_error2;
|
|
unsigned short cal_type;
|
|
unsigned int rising_threshold, falling_threshold;
|
|
unsigned int reg_off, bit_off;
|
|
enum thermal_trip_type type;
|
|
int temp, temp_hist, threshold_code;
|
|
int i, sensor, count = 0, interrupt_count;
|
|
|
|
trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(0));
|
|
cal_type = (trim_info >> EXYNOS_TMU_CALIB_SEL_SHIFT) & EXYNOS_TMU_CALIB_SEL_MASK;
|
|
|
|
for (sensor = 0; sensor < TOTAL_SENSORS; sensor++) {
|
|
|
|
if (!(data->sensors & (1 << sensor)))
|
|
continue;
|
|
|
|
/* Read the sensor error value from TRIMINFOX */
|
|
trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(sensor));
|
|
temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK;
|
|
temp_error2 = (trim_info >> EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) & EXYNOS_TMU_TEMP_MASK;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
interrupt_count = 0;
|
|
/* Write temperature code for rising and falling threshold */
|
|
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;
|
|
|
|
reg_off = (interrupt_count / 2) * 4;
|
|
bit_off = ((interrupt_count + 1) % 2) * 16;
|
|
|
|
if (sensor == 0)
|
|
reg_off += EXYNOS_TMU_REG_THD_TEMP0;
|
|
else if (sensor < 8)
|
|
reg_off += EXYNOS_TMU_REG_THD_TEMP1 + (sensor - 1) * 0x20;
|
|
else
|
|
reg_off += EXYNOS_TMU_REG_THD_TEMP8 + (sensor - 8) * 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 + reg_off);
|
|
rising_threshold &= ~(EXYNOS_TMU_TEMP_MASK << bit_off);
|
|
rising_threshold |= threshold_code << bit_off;
|
|
writel(rising_threshold, data->base + 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 = readl(data->base + reg_off + 0x10);
|
|
falling_threshold &= ~(EXYNOS_TMU_TEMP_MASK << bit_off);
|
|
falling_threshold |= threshold_code << bit_off;
|
|
writel(falling_threshold, data->base + reg_off + 0x10);
|
|
interrupt_count++;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
data->tmu_clear_irqs(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos9610_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 trim_info, temp_error1, temp_error2;
|
|
unsigned short cal_type;
|
|
unsigned int rising_threshold, falling_threshold;
|
|
unsigned int reg_off, bit_off;
|
|
enum thermal_trip_type type;
|
|
int temp, temp_hist, threshold_code;
|
|
int i, sensor, count = 0, interrupt_count;
|
|
|
|
trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(0));
|
|
cal_type = (trim_info >> EXYNOS_TMU_CALIB_SEL_SHIFT) & EXYNOS_TMU_CALIB_SEL_MASK;
|
|
|
|
for (sensor = 0; sensor < TOTAL_SENSORS; sensor++) {
|
|
|
|
if (!(data->sensors & (1 << sensor)))
|
|
continue;
|
|
|
|
/* Read the sensor error value from TRIMINFOX */
|
|
trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(sensor));
|
|
temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK;
|
|
temp_error2 = (trim_info >> EXYNOS_TMU_TRIMINFO_85_P0_SHIFT) & EXYNOS_TMU_TEMP_MASK;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
interrupt_count = 0;
|
|
/* Write temperature code for rising and falling threshold */
|
|
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;
|
|
|
|
reg_off = (interrupt_count / 2) * 4;
|
|
bit_off = ((interrupt_count + 1) % 2) * 16;
|
|
|
|
if (sensor == 0)
|
|
reg_off += EXYNOS_TMU_REG_THD_TEMP0;
|
|
else if (sensor < 8)
|
|
reg_off += EXYNOS_TMU_REG_THD_TEMP1 + (sensor - 1) * 0x20;
|
|
else
|
|
reg_off += EXYNOS_TMU_REG_THD_TEMP8 + (sensor - 8) * 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 + reg_off);
|
|
rising_threshold &= ~(EXYNOS_TMU_TEMP_MASK << bit_off);
|
|
rising_threshold |= threshold_code << bit_off;
|
|
writel(rising_threshold, data->base + 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 = readl(data->base + reg_off + 0x10);
|
|
falling_threshold &= ~(EXYNOS_TMU_TEMP_MASK << bit_off);
|
|
falling_threshold |= threshold_code << bit_off;
|
|
writel(falling_threshold, data->base + reg_off + 0x10);
|
|
interrupt_count++;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
data->tmu_clear_irqs(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tmu_core_enable(struct platform_device *pdev)
|
|
{
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
unsigned int ctrl;
|
|
|
|
/* set TRIP_EN, CORE_EN */
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_CONTROL);
|
|
ctrl |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT);
|
|
ctrl |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
|
|
writel(ctrl, data->base + EXYNOS_TMU_REG_CONTROL);
|
|
}
|
|
|
|
static void tmu_core_disable(struct platform_device *pdev)
|
|
{
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
unsigned int ctrl;
|
|
|
|
/* reset TRIP_EN, CORE_EN */
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_CONTROL);
|
|
ctrl &= ~(1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT);
|
|
ctrl &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
|
|
writel(ctrl, data->base + EXYNOS_TMU_REG_CONTROL);
|
|
}
|
|
|
|
static void tmu_irqs_enable(struct platform_device *pdev)
|
|
{
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
struct thermal_zone_device *tz = data->tzd;
|
|
int i, offset, interrupt_count = 0;
|
|
unsigned int interrupt_en = 0, bit_off;
|
|
enum thermal_trip_type type;
|
|
|
|
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;
|
|
|
|
/* rising interrupt */
|
|
interrupt_en |= of_thermal_is_trip_valid(tz, i) << bit_off;
|
|
interrupt_count++;
|
|
}
|
|
|
|
/* falling interrupt */
|
|
interrupt_en |= (interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT);
|
|
|
|
for (i = 0; i < TOTAL_SENSORS; i++) {
|
|
if (!(data->sensors & (1 << i)))
|
|
continue;
|
|
|
|
if (i < 5)
|
|
offset = EXYNOS_TMU_REG_INTEN0 + EXYNOS_TMU_REG_INTEN_OFFSET * i;
|
|
else if (i < 8)
|
|
offset = EXYNOS_TMU_REG_INTEN5 + EXYNOS_TMU_REG_INTEN_OFFSET * (i - 5);
|
|
else
|
|
offset = EXYNOS_TMU_REG_INTEN8 + EXYNOS_TMU_REG_INTEN_OFFSET * (i - 8);
|
|
|
|
writel(interrupt_en, data->base + offset);
|
|
}
|
|
}
|
|
|
|
static void tmu_irqs_disable(struct platform_device *pdev)
|
|
{
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
int i, offset;
|
|
|
|
for (i = 0; i < TOTAL_SENSORS; i++) {
|
|
if (!(data->sensors & (1 << i)))
|
|
continue;
|
|
|
|
if (i < 5)
|
|
offset = EXYNOS_TMU_REG_INTEN0 + EXYNOS_TMU_REG_INTEN_OFFSET * i;
|
|
else if (i < 8)
|
|
offset = EXYNOS_TMU_REG_INTEN5 + EXYNOS_TMU_REG_INTEN_OFFSET * (i - 5);
|
|
else
|
|
offset = EXYNOS_TMU_REG_INTEN8 + EXYNOS_TMU_REG_INTEN_OFFSET * (i - 8);
|
|
|
|
writel(0, data->base + offset);
|
|
}
|
|
}
|
|
|
|
static void exynos9810_tmu_control(struct platform_device *pdev, bool on)
|
|
{
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
unsigned int trim, ctrl, con1, avgc;
|
|
unsigned int t_buf_vref_sel, t_buf_slope_sel, avg_mode;
|
|
unsigned int counter_value;
|
|
|
|
tmu_core_disable(pdev);
|
|
tmu_irqs_disable(pdev);
|
|
|
|
if (!on)
|
|
return;
|
|
|
|
trim = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(0));
|
|
t_buf_vref_sel = (trim >> EXYNOS_TMU_T_BUF_VREF_SEL_SHIFT)
|
|
& EXYNOS_TMU_T_BUF_VREF_SEL_MASK;
|
|
|
|
trim = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(1));
|
|
t_buf_slope_sel = (trim >> EXYNOS_TMU_T_BUF_SLOPE_SEL_SHIFT)
|
|
& EXYNOS_TMU_T_BUF_SLOPE_SEL_MASK;
|
|
|
|
trim = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(2));
|
|
avg_mode = (trim >> EXYNOS_TMU_AVG_CON_SHIFT) & EXYNOS_TMU_AVG_MODE_MASK;
|
|
|
|
/* set BUf_VREF_SEL, BUF_SLOPE_SEL */
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_CONTROL);
|
|
ctrl &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT);
|
|
ctrl &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT);
|
|
ctrl |= t_buf_vref_sel << EXYNOS_TMU_REF_VOLTAGE_SHIFT;
|
|
ctrl |= t_buf_slope_sel << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT;
|
|
writel(ctrl, data->base + EXYNOS_TMU_REG_CONTROL);
|
|
|
|
/* set NUM_PROBE, reset LPI_MODE_EN */
|
|
con1 = readl(data->base + EXYNOS_TMU_REG_CONTROL1);
|
|
con1 &= ~(EXYNOS_TMU_NUM_PROBE_MASK << EXYNOS_TMU_NUM_PROBE_SHIFT);
|
|
con1 &= ~(EXYNOS_TMU_LPI_MODE_MASK << EXYNOS_TMU_LPI_MODE_SHIFT);
|
|
con1 |= (data->num_probe << EXYNOS_TMU_NUM_PROBE_SHIFT);
|
|
writel(con1, data->base + EXYNOS_TMU_REG_CONTROL1);
|
|
|
|
/* set EN_DEM, AVG_MODE */
|
|
avgc = readl(data->base + EXYNOS_TMU_REG_AVG_CON);
|
|
avgc &= ~(EXYNOS_TMU_AVG_MODE_MASK);
|
|
avgc &= ~(EXYNOS_TMU_DEM_ENABLE << EXYNOS_TMU_DEM_SHIFT);
|
|
if (avg_mode) {
|
|
avgc |= avg_mode;
|
|
avgc |= (EXYNOS_TMU_DEM_ENABLE << EXYNOS_TMU_DEM_SHIFT);
|
|
}
|
|
writel(avgc, data->base + EXYNOS_TMU_REG_AVG_CON);
|
|
|
|
/* set COUNTER_VALUE */
|
|
counter_value = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE0);
|
|
counter_value &= ~(EXYNOS_TMU_EN_TEMP_SEN_OFF_MASK << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT);
|
|
counter_value |= EXYNOS_TMU_TEM1051X_SENSE_VALUE << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT;
|
|
writel(counter_value, data->base + EXYNOS_TMU_REG_COUNTER_VALUE0);
|
|
|
|
counter_value = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE1);
|
|
counter_value &= ~(EXYNOS_TMU_CLK_SENSE_ON_MASK << EXYNOS_TMU_CLK_SENSE_ON_SHIFT);
|
|
counter_value |= EXYNOS_TMU_TEM1051X_SENSE_VALUE << EXYNOS_TMU_CLK_SENSE_ON_SHIFT;
|
|
writel(counter_value, data->base + EXYNOS_TMU_REG_COUNTER_VALUE1);
|
|
|
|
/* set TRIM0 BGR_I/VREF/VBE_I */
|
|
/* write TRIM0 values read from TMU_TOP to each TMU_TOP and TMU_SUB */
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIM0);
|
|
ctrl &= ~(EXYNOS_TMU_BGRI_TRIM_MASK << EXYNOS_TMU_BGRI_TRIM_SHIFT);
|
|
ctrl &= ~(EXYNOS_TMU_VREF_TRIM_MASK << EXYNOS_TMU_VREF_TRIM_SHIFT);
|
|
ctrl &= ~(EXYNOS_TMU_VBEI_TRIM_MASK << EXYNOS_TMU_VBEI_TRIM_SHIFT);
|
|
ctrl |= (t_bgri_trim << EXYNOS_TMU_BGRI_TRIM_SHIFT);
|
|
ctrl |= (t_vref_trim << EXYNOS_TMU_VREF_TRIM_SHIFT);
|
|
ctrl |= (t_vbei_trim << EXYNOS_TMU_VBEI_TRIM_SHIFT);
|
|
writel(ctrl, data->base + EXYNOS_TMU_REG_TRIM0);
|
|
|
|
tmu_irqs_enable(pdev);
|
|
tmu_core_enable(pdev);
|
|
}
|
|
|
|
static void exynos9610_tmu_control(struct platform_device *pdev, bool on)
|
|
{
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
unsigned int trim, ctrl, con1, avgc;
|
|
unsigned int t_buf_vref_sel, t_buf_slope_sel, avg_mode;
|
|
unsigned int counter_value;
|
|
|
|
tmu_core_disable(pdev);
|
|
tmu_irqs_disable(pdev);
|
|
|
|
if (!on)
|
|
return;
|
|
|
|
trim = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(0));
|
|
t_buf_vref_sel = (trim >> EXYNOS_TMU_T_BUF_VREF_SEL_SHIFT)
|
|
& EXYNOS_TMU_T_BUF_VREF_SEL_MASK;
|
|
|
|
trim = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(1));
|
|
t_buf_slope_sel = (trim >> EXYNOS_TMU_T_BUF_SLOPE_SEL_SHIFT)
|
|
& EXYNOS_TMU_T_BUF_SLOPE_SEL_MASK;
|
|
|
|
trim = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(2));
|
|
avg_mode = (trim >> EXYNOS_TMU_AVG_CON_SHIFT) & EXYNOS_TMU_AVG_MODE_MASK;
|
|
|
|
/* set BUf_VREF_SEL, BUF_SLOPE_SEL */
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_CONTROL);
|
|
ctrl &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT);
|
|
ctrl &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT);
|
|
ctrl |= t_buf_vref_sel << EXYNOS_TMU_REF_VOLTAGE_SHIFT;
|
|
ctrl |= t_buf_slope_sel << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT;
|
|
writel(ctrl, data->base + EXYNOS_TMU_REG_CONTROL);
|
|
|
|
/* set NUM_PROBE, reset LPI_MODE_EN */
|
|
con1 = readl(data->base + EXYNOS_TMU_REG_CONTROL1);
|
|
con1 &= ~(EXYNOS_TMU_NUM_PROBE_MASK << EXYNOS_TMU_NUM_PROBE_SHIFT);
|
|
con1 &= ~(EXYNOS_TMU_LPI_MODE_MASK << EXYNOS_TMU_LPI_MODE_SHIFT);
|
|
con1 |= (data->num_probe << EXYNOS_TMU_NUM_PROBE_SHIFT);
|
|
writel(con1, data->base + EXYNOS_TMU_REG_CONTROL1);
|
|
|
|
/* set EN_DEM, AVG_MODE */
|
|
avgc = readl(data->base + EXYNOS_TMU_REG_AVG_CON);
|
|
avgc &= ~(EXYNOS_TMU_AVG_MODE_MASK);
|
|
avgc &= ~(EXYNOS_TMU_DEM_ENABLE << EXYNOS_TMU_DEM_SHIFT);
|
|
if (avg_mode) {
|
|
avgc |= avg_mode;
|
|
avgc |= (EXYNOS_TMU_DEM_ENABLE << EXYNOS_TMU_DEM_SHIFT);
|
|
}
|
|
writel(avgc, data->base + EXYNOS_TMU_REG_AVG_CON);
|
|
|
|
/* set COUNTER_VALUE */
|
|
counter_value = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE0);
|
|
counter_value &= ~(EXYNOS_TMU_EN_TEMP_SEN_OFF_MASK << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT);
|
|
counter_value |= EXYNOS_TMU_TEM1002X_SENSE_VALUE << EXYNOS_TMU_EN_TEMP_SEN_OFF_SHIFT;
|
|
writel(counter_value, data->base + EXYNOS_TMU_REG_COUNTER_VALUE0);
|
|
|
|
counter_value = readl(data->base + EXYNOS_TMU_REG_COUNTER_VALUE1);
|
|
counter_value &= ~(EXYNOS_TMU_CLK_SENSE_ON_MASK << EXYNOS_TMU_CLK_SENSE_ON_SHIFT);
|
|
counter_value |= EXYNOS_TMU_TEM1002X_SENSE_VALUE << EXYNOS_TMU_CLK_SENSE_ON_SHIFT;
|
|
writel(counter_value, data->base + EXYNOS_TMU_REG_COUNTER_VALUE1);
|
|
|
|
/* set TRIM0 BGR_I/VREF/VBE_I */
|
|
/* write TRIM0 values read from TMU_TOP to each TMU_TOP and TMU_SUB */
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIM0);
|
|
ctrl &= ~(EXYNOS_TMU_BGRI_TRIM_MASK << EXYNOS_TMU_BGRI_TRIM_SHIFT);
|
|
ctrl &= ~(EXYNOS_TMU_VREF_TRIM_MASK << EXYNOS_TMU_VREF_TRIM_SHIFT);
|
|
ctrl &= ~(EXYNOS_TMU_VBEI_TRIM_MASK << EXYNOS_TMU_VBEI_TRIM_SHIFT);
|
|
ctrl |= (t_bgri_trim << EXYNOS_TMU_BGRI_TRIM_SHIFT);
|
|
ctrl |= (t_vref_trim << EXYNOS_TMU_VREF_TRIM_SHIFT);
|
|
ctrl |= (t_vbei_trim << EXYNOS_TMU_VBEI_TRIM_SHIFT);
|
|
writel(ctrl, data->base + EXYNOS_TMU_REG_TRIM0);
|
|
|
|
tmu_irqs_enable(pdev);
|
|
tmu_core_enable(pdev);
|
|
}
|
|
|
|
#define MCINFO_LOG_THRESHOLD (4)
|
|
|
|
static int exynos_get_temp(void *p, int *temp)
|
|
{
|
|
struct exynos_tmu_data *data = p;
|
|
#ifndef CONFIG_EXYNOS_ACPM_THERMAL
|
|
struct thermal_cooling_device *cdev = NULL;
|
|
struct thermal_zone_device *tz;
|
|
struct thermal_instance *instance;
|
|
#endif
|
|
#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 || !data->enabled)
|
|
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);
|
|
|
|
#ifndef CONFIG_EXYNOS_ACPM_THERMAL
|
|
tz = data->tzd;
|
|
|
|
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
|
|
if (instance->cdev) {
|
|
cdev = instance->cdev;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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);
|
|
#endif
|
|
|
|
dbg_snapshot_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)
|
|
dbg_snapshot_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, "BIG", THERMAL_NAME_LENGTH)) {
|
|
exynos_get_temp(data, &temp[0]);
|
|
temp[0] /= 1000;
|
|
} else if (!strncasecmp(data->tmu_name, "LITTLE", THERMAL_NAME_LENGTH)) {
|
|
exynos_get_temp(data, &temp[1]);
|
|
temp[1] /= 1000;
|
|
} else if (!strncasecmp(data->tmu_name, "G3D", 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
|
|
|
|
static int exynos_get_trend(void *p, int trip, enum thermal_trend *trend)
|
|
{
|
|
struct exynos_tmu_data *data = p;
|
|
struct thermal_zone_device *tz = data->tzd;
|
|
int trip_temp, ret = 0;
|
|
|
|
ret = tz->ops->get_trip_temp(tz, trip, &trip_temp);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (tz->temperature >= trip_temp)
|
|
*trend = THERMAL_TREND_RAISE_FULL;
|
|
else
|
|
*trend = THERMAL_TREND_DROP_FULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_THERMAL_EMULATION
|
|
static void exynos9810_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 void exynos9610_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
|
|
static int exynos_tmu_set_emulation(void *drv_data, int temp)
|
|
{ return -EINVAL; }
|
|
#endif /* CONFIG_THERMAL_EMULATION */
|
|
|
|
#if defined(CONFIG_SOC_EXYNOS9810) || defined(CONFIG_SOC_EXYNOS9610)
|
|
static bool cpufreq_limited = false;
|
|
static struct pm_qos_request thermal_cpu_limit_request;
|
|
#endif
|
|
|
|
static int exynos9810_tmu_read(struct exynos_tmu_data *data)
|
|
{
|
|
int temp = 0, stat = 0;
|
|
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
exynos_acpm_tmu_set_read_temp(data->tzd->id, &temp, &stat);
|
|
#endif
|
|
|
|
if (data->hotplug_enable) {
|
|
if ((stat == 2) && !cpufreq_limited) {
|
|
pm_qos_update_request(&thermal_cpu_limit_request,
|
|
data->limited_frequency);
|
|
cpufreq_limited = true;
|
|
} else if ((stat == 0 || stat == 1) && cpufreq_limited) {
|
|
pm_qos_update_request(&thermal_cpu_limit_request,
|
|
PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE);
|
|
cpufreq_limited = false;
|
|
}
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
static int exynos9610_tmu_read(struct exynos_tmu_data *data)
|
|
{
|
|
int temp = 0, stat = 0;
|
|
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
exynos_acpm_tmu_set_read_temp(data->tzd->id, &temp, &stat);
|
|
#endif
|
|
|
|
if (data->limited_frequency) {
|
|
/* If cpufreq_limited flag is low and temp is higher than limited_threshold,
|
|
* limited max frequency.
|
|
* And cpufreq_limited flag is high and temp is lower than
|
|
* limited_threshold_release, release max frequency.
|
|
*/
|
|
if ((temp > data->limited_threshold) && !cpufreq_limited) {
|
|
pm_qos_update_request(&thermal_cpu_limit_request,
|
|
data->limited_frequency);
|
|
cpufreq_limited = true;
|
|
dbg_snapshot_thermal(data->pdata, temp, "limited", data->limited_frequency);
|
|
}
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
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);
|
|
|
|
data->tmu_clear_irqs(data);
|
|
|
|
mutex_unlock(&data->lock);
|
|
enable_irq(data->irq);
|
|
}
|
|
|
|
static void exynos9810_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)))
|
|
continue;
|
|
|
|
if (i < 5)
|
|
pend_reg = EXYNOS_TMU_REG_INTPEND0 + EXYNOS_TMU_REG_INTPEN_OFFSET * i;
|
|
else if (i < 8)
|
|
pend_reg = EXYNOS_TMU_REG_INTPEND5 + EXYNOS_TMU_REG_INTPEN_OFFSET * (i - 5);
|
|
else
|
|
pend_reg = EXYNOS_TMU_REG_INTPEND8 + EXYNOS_TMU_REG_INTPEN_OFFSET * (i - 8);
|
|
|
|
val_irq = readl(data->base + pend_reg);
|
|
writel(val_irq, data->base + pend_reg);
|
|
}
|
|
}
|
|
|
|
static void exynos9610_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)))
|
|
continue;
|
|
|
|
if (i < 5)
|
|
pend_reg = EXYNOS_TMU_REG_INTPEND0 + EXYNOS_TMU_REG_INTPEN_OFFSET * i;
|
|
else if (i < 8)
|
|
pend_reg = EXYNOS_TMU_REG_INTPEND5 + EXYNOS_TMU_REG_INTPEN_OFFSET * (i - 5);
|
|
else
|
|
pend_reg = EXYNOS_TMU_REG_INTPEND8 + EXYNOS_TMU_REG_INTPEN_OFFSET * (i - 8);
|
|
|
|
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);
|
|
queue_work(thermal_irq_wq, &data->irq_work);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#ifndef CONFIG_EXYNOS_ACPM_THERMAL
|
|
static int exynos_pm_notifier(struct notifier_block *notifier,
|
|
unsigned long event, void *v)
|
|
{
|
|
struct exynos_tmu_data *devnode;
|
|
struct thermal_cooling_device *cdev = NULL;
|
|
struct thermal_zone_device *tz;
|
|
struct thermal_instance *instance;
|
|
|
|
switch (event) {
|
|
case PM_SUSPEND_PREPARE:
|
|
mutex_lock(&thermal_suspend_lock);
|
|
suspended = true;
|
|
list_for_each_entry(devnode, &dtm_dev_list, node) {
|
|
tz = devnode->tzd;
|
|
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
|
|
if (instance->cdev) {
|
|
cdev = instance->cdev;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
tz = devnode->tzd;
|
|
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
|
|
if (instance->cdev) {
|
|
cdev = instance->cdev;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|
|
#endif
|
|
|
|
static int exynos_tmu_boost_callback(unsigned int cpu)
|
|
{
|
|
struct thermal_zone_device *tz;
|
|
struct exynos_tmu_data *devnode;
|
|
|
|
list_for_each_entry(devnode, &dtm_dev_list, node) {
|
|
if (devnode->id == 0) {
|
|
tz = devnode->tzd;
|
|
thermal_zone_device_update(tz, THERMAL_DEVICE_POWER_CAPABILITY_CHANGED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id exynos_tmu_match[] = {
|
|
{ .compatible = "samsung,exynos9810-tmu", },
|
|
{ .compatible = "samsung,exynos9610-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,exynos9810-tmu"))
|
|
return SOC_ARCH_EXYNOS9810;
|
|
if (of_device_is_compatible(np, "samsung,exynos9610-tmu"))
|
|
return SOC_ARCH_EXYNOS9610;
|
|
|
|
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;
|
|
unsigned 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 = (int)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_EXYNOS9810:
|
|
data->tmu_initialize = exynos9810_tmu_initialize;
|
|
data->tmu_control = exynos9810_tmu_control;
|
|
data->tmu_read = exynos9810_tmu_read;
|
|
data->tmu_set_emulation = exynos9810_tmu_set_emulation;
|
|
data->tmu_clear_irqs = exynos9810_tmu_clear_irqs;
|
|
data->ntrip = 8;
|
|
break;
|
|
case SOC_ARCH_EXYNOS9610:
|
|
data->tmu_initialize = exynos9610_tmu_initialize;
|
|
data->tmu_control = exynos9610_tmu_control;
|
|
data->tmu_read = exynos9610_tmu_read;
|
|
data->tmu_set_emulation = exynos9610_tmu_set_emulation;
|
|
data->tmu_clear_irqs = exynos9610_tmu_clear_irqs;
|
|
data->ntrip = 8;
|
|
break;
|
|
default:
|
|
dev_err(&pdev->dev, "Platform not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_throttle_cpu_hotplug(void *p, int temp)
|
|
{
|
|
struct exynos_tmu_data *data = p;
|
|
int ret = 0;
|
|
struct cpumask mask;
|
|
unsigned int online_cpus;
|
|
|
|
temp = temp / MCELSIUS;
|
|
|
|
mutex_lock(&data->hotplug_lock);
|
|
|
|
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.
|
|
*/
|
|
exynos_cpuhp_request("DTM", *cpu_possible_mask, 0);
|
|
is_cpu_hotplugged_out = false;
|
|
|
|
online_cpus = *(unsigned int *)cpumask_bits(cpu_online_mask);
|
|
pr_info("CPU HOTPLUG IN : 0x%x, Temp : %d\n", online_cpus, temp);
|
|
}
|
|
} 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;
|
|
cpumask_and(&mask, cpu_possible_mask, cpu_coregroup_mask(0));
|
|
exynos_cpuhp_request("DTM", mask, 0);
|
|
|
|
online_cpus = *(unsigned int *)cpumask_bits(cpu_online_mask);
|
|
pr_info("CPU HOTPLUG OUT : 0x%x, Temp : %d\n", online_cpus, temp);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&data->hotplug_lock);
|
|
|
|
#if defined(CONFIG_SOC_EXYNOS9610)
|
|
if (data->limited_frequency) {
|
|
/* cpufreq_limited flag is high and temp is lower than
|
|
* limited_threshold_release, release max frequency.
|
|
*/
|
|
if ((temp < data->limited_threshold_release) && cpufreq_limited) {
|
|
pm_qos_update_request(&thermal_cpu_limit_request,
|
|
PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE);
|
|
cpufreq_limited = false;
|
|
dbg_snapshot_thermal(data->pdata, temp, "release_limited",
|
|
PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const 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 const struct thermal_zone_of_device_ops exynos_sensor_ops = {
|
|
.get_temp = exynos_get_temp,
|
|
.set_emul_temp = exynos_tmu_set_emulation,
|
|
.get_trend = exynos_get_trend,
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
#define PARAM_NAME_LENGTH 25
|
|
#define FRAC_BITS 10 /* FRAC_BITS should be same with power_allocator */
|
|
|
|
#if defined(CONFIG_ECT)
|
|
static int exynos_tmu_ect_get_param(struct ect_pidtm_block *pidtm_block, char *name)
|
|
{
|
|
int i;
|
|
int param_value = -1;
|
|
|
|
for (i = 0; i < pidtm_block->num_of_parameter; i++) {
|
|
if (!strncasecmp(pidtm_block->param_name_list[i], name, PARAM_NAME_LENGTH)) {
|
|
param_value = pidtm_block->param_value_list[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return param_value;
|
|
}
|
|
|
|
static int exynos_tmu_parse_ect(struct exynos_tmu_data *data)
|
|
{
|
|
struct thermal_zone_device *tz = data->tzd;
|
|
struct __thermal_zone *__tz;
|
|
|
|
if (!tz)
|
|
return -EINVAL;
|
|
|
|
__tz = (struct __thermal_zone *)tz->devdata;
|
|
|
|
if (strncasecmp(tz->tzp->governor_name, "power_allocator",
|
|
THERMAL_NAME_LENGTH)) {
|
|
/* if governor is not power_allocator */
|
|
void *thermal_block;
|
|
struct ect_ap_thermal_function *function;
|
|
int i, temperature;
|
|
int hotplug_threshold_temp = 0, hotplug_flag = 0;
|
|
unsigned int freq;
|
|
|
|
thermal_block = ect_get_block(BLOCK_AP_THERMAL);
|
|
if (thermal_block == NULL) {
|
|
pr_err("Failed to get thermal block");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_info("%s %d thermal zone_name = %s\n", __func__, __LINE__, tz->type);
|
|
|
|
function = ect_ap_thermal_get_function(thermal_block, tz->type);
|
|
if (function == NULL) {
|
|
pr_err("Failed to get thermal block %s", tz->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
__tz->ntrips = __tz->num_tbps = function->num_of_range;
|
|
pr_info("Trip count parsed from ECT : %d, zone : %s", function->num_of_range, tz->type);
|
|
|
|
for (i = 0; i < function->num_of_range; ++i) {
|
|
temperature = function->range_list[i].lower_bound_temperature;
|
|
freq = function->range_list[i].max_frequency;
|
|
__tz->trips[i].temperature = temperature * MCELSIUS;
|
|
__tz->tbps[i].value = freq;
|
|
|
|
pr_info("Parsed From ECT : [%d] Temperature : %d, frequency : %u\n",
|
|
i, temperature, freq);
|
|
|
|
if (function->range_list[i].flag != hotplug_flag) {
|
|
if (function->range_list[i].flag != hotplug_flag) {
|
|
hotplug_threshold_temp = temperature;
|
|
hotplug_flag = function->range_list[i].flag;
|
|
data->hotplug_out_threshold = temperature;
|
|
|
|
if (i)
|
|
data->hotplug_in_threshold = function->range_list[i-1].lower_bound_temperature;
|
|
|
|
pr_info("[ECT]hotplug_threshold : %d\n", hotplug_threshold_temp);
|
|
pr_info("[ECT]hotplug_in_threshold : %d\n", data->hotplug_in_threshold);
|
|
pr_info("[ECT]hotplug_out_threshold : %d\n", data->hotplug_out_threshold);
|
|
}
|
|
}
|
|
|
|
if (hotplug_threshold_temp != 0)
|
|
data->hotplug_enable = true;
|
|
else
|
|
data->hotplug_enable = false;
|
|
|
|
}
|
|
} else {
|
|
void *block;
|
|
struct ect_pidtm_block *pidtm_block;
|
|
int i, temperature, value;
|
|
int hotplug_out_threshold = 0, hotplug_in_threshold = 0;
|
|
|
|
block = ect_get_block(BLOCK_PIDTM);
|
|
if (block == NULL) {
|
|
pr_err("Failed to get PIDTM block");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_info("%s %d thermal zone_name = %s\n", __func__, __LINE__, tz->type);
|
|
|
|
pidtm_block = ect_pidtm_get_block(block, tz->type);
|
|
if (pidtm_block == NULL) {
|
|
pr_err("Failed to get PIDTM block %s", tz->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
__tz->ntrips = pidtm_block->num_of_temperature;
|
|
|
|
for (i = 0; i < pidtm_block->num_of_temperature; ++i) {
|
|
temperature = pidtm_block->temperature_list[i];
|
|
__tz->trips[i].temperature = temperature * MCELSIUS;
|
|
|
|
pr_info("Parsed From ECT : [%d] Temperature : %d\n", i, temperature);
|
|
}
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "k_po")) != -1) {
|
|
pr_info("Parse from ECT k_po: %d\n", value);
|
|
tz->tzp->k_po = value << FRAC_BITS;
|
|
} else
|
|
pr_err("Fail to parse k_po parameter\n");
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "k_pu")) != -1) {
|
|
pr_info("Parse from ECT k_pu: %d\n", value);
|
|
tz->tzp->k_pu = value << FRAC_BITS;
|
|
} else
|
|
pr_err("Fail to parse k_pu parameter\n");
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "k_i")) != -1) {
|
|
pr_info("Parse from ECT k_i: %d\n", value);
|
|
tz->tzp->k_i = value << FRAC_BITS;
|
|
} else
|
|
pr_err("Fail to parse k_i parameter\n");
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "i_max")) != -1) {
|
|
pr_info("Parse from ECT i_max: %d\n", value);
|
|
tz->tzp->integral_max = value;
|
|
} else
|
|
pr_err("Fail to parse i_max parameter\n");
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "integral_cutoff")) != -1) {
|
|
pr_info("Parse from ECT integral_cutoff: %d\n", value);
|
|
tz->tzp->integral_cutoff = value;
|
|
} else
|
|
pr_err("Fail to parse integral_cutoff parameter\n");
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "p_control_t")) != -1) {
|
|
pr_info("Parse from ECT p_control_t: %d\n", value);
|
|
tz->tzp->sustainable_power = value;
|
|
} else
|
|
pr_err("Fail to parse p_control_t parameter\n");
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "hotplug_out_threshold")) != -1) {
|
|
pr_info("Parse from ECT hotplug_out_threshold: %d\n", value);
|
|
hotplug_out_threshold = value;
|
|
}
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "hotplug_in_threshold")) != -1) {
|
|
pr_info("Parse from ECT hotplug_in_threshold: %d\n", value);
|
|
hotplug_in_threshold = value;
|
|
}
|
|
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "limited_frequency")) != -1) {
|
|
pr_info("Parse from ECT limited_frequency: %d\n", value);
|
|
data->limited_frequency = value;
|
|
} else {
|
|
data->limited_frequency = 0;
|
|
pr_err("Fail to parse limited_frequency parameter\n");
|
|
}
|
|
|
|
#if defined(CONFIG_SOC_EXYNOS9610)
|
|
if (data->limited_frequency) {
|
|
/* Check limited_threshold to ect, if limited_frequency value is existed */
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "limited_threshold")) != -1) {
|
|
pr_info("Parse from ECT limited_threshold: %d\n", value);
|
|
data->limited_threshold = value;
|
|
} else {
|
|
pr_err("Fail to parse ECT limited_threshold, disable limit frequency mode\n");
|
|
data->limited_frequency = 0;
|
|
}
|
|
|
|
/* Check limited_threshold_release to ect, if limited_frequency value is existed */
|
|
if ((value = exynos_tmu_ect_get_param(pidtm_block, "limited_threshold_release")) != -1) {
|
|
pr_info("Parse from ECT limited_threshold_release: %d\n", value);
|
|
data->limited_threshold_release = value;
|
|
} else {
|
|
pr_err("Fail to parse ECT limited_threshold_release, disable limit frequency mode\n");
|
|
data->limited_frequency = 0;
|
|
}
|
|
} else
|
|
pr_err("Do not parsing limited_threshold and limited_threshold_release parameter\n");
|
|
#endif
|
|
|
|
if (hotplug_out_threshold != 0 && hotplug_in_threshold != 0) {
|
|
data->hotplug_out_threshold = hotplug_out_threshold;
|
|
data->hotplug_in_threshold = hotplug_in_threshold;
|
|
data->hotplug_enable = true;
|
|
} else
|
|
data->hotplug_enable = false;
|
|
}
|
|
return 0;
|
|
};
|
|
#endif
|
|
|
|
#ifdef CONFIG_MALI_DEBUG_KERNEL_SYSFS
|
|
struct exynos_tmu_data *gpu_thermal_data;
|
|
#endif
|
|
|
|
static int exynos_tmu_probe(struct platform_device *pdev)
|
|
{
|
|
struct exynos_tmu_data *data;
|
|
struct workqueue_attrs attr;
|
|
unsigned int ctrl;
|
|
int ret;
|
|
|
|
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;
|
|
|
|
/* Regist high priorty workqueue */
|
|
if (!thermal_irq_wq) {
|
|
attr.nice = 0;
|
|
attr.no_numa = true;
|
|
cpumask_copy(attr.cpumask, cpu_coregroup_mask(0));
|
|
|
|
thermal_irq_wq = alloc_workqueue("%s", WQ_HIGHPRI | WQ_UNBOUND |\
|
|
WQ_MEM_RECLAIM | WQ_FREEZABLE,
|
|
0, "thermal_irq");
|
|
apply_workqueue_attrs(thermal_irq_wq, &attr);
|
|
}
|
|
|
|
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) {
|
|
exynos_cpuhp_register("DTM", *cpu_online_mask, 0);
|
|
|
|
#if defined(CONFIG_SOC_EXYNOS9810) || defined(CONFIG_SOC_EXYNOS9610)
|
|
pm_qos_add_request(&thermal_cpu_limit_request,
|
|
PM_QOS_CLUSTER1_FREQ_MAX,
|
|
PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE);
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#if defined(CONFIG_ECT)
|
|
exynos_tmu_parse_ect(data);
|
|
#endif
|
|
|
|
data->num_probe = (readl(data->base + EXYNOS_TMU_REG_CONTROL1) >> EXYNOS_TMU_NUM_PROBE_SHIFT)
|
|
& EXYNOS_TMU_NUM_PROBE_MASK;
|
|
|
|
#if defined(CONFIG_SOC_EXYNOS9810)
|
|
if (data->id == 0) {
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(3));
|
|
t_bgri_trim = (ctrl >> EXYNOS_TMU_T_TRIM0_SHIFT) & EXYNOS_TMU_T_TRIM0_MASK;
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(4));
|
|
t_vref_trim = (ctrl >> EXYNOS_TMU_T_TRIM0_SHIFT) & EXYNOS_TMU_T_TRIM0_MASK;
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(5));
|
|
t_vbei_trim = (ctrl >> EXYNOS_TMU_T_TRIM0_SHIFT) & EXYNOS_TMU_T_TRIM0_MASK;
|
|
}
|
|
#elif defined(CONFIG_SOC_EXYNOS9610)
|
|
if (data->id == 0) {
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(3));
|
|
t_bgri_trim = (ctrl >> EXYNOS_TMU_T_TRIM0_SHIFT) & EXYNOS_TMU_T_TRIM0_MASK;
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(3));
|
|
t_vref_trim = (ctrl >> EXYNOS_TMU_T_VREF_TRIM_SHIFT) & EXYNOS_TMU_T_VREF_TRIM_MASK;
|
|
ctrl = readl(data->base + EXYNOS_TMU_REG_TRIMINFO(3));
|
|
t_vbei_trim = (ctrl >> EXYNOS_TMU_T_VBE_I_TRIM_SHIFT) & EXYNOS_TMU_T_VBE_I_TRIM_MASK;
|
|
}
|
|
#endif
|
|
|
|
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 | IRQF_GIC_MULTI_TARGET, 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);
|
|
num_of_devices++;
|
|
mutex_unlock(&data->lock);
|
|
|
|
if (list_is_singular(&dtm_dev_list)) {
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
exynos_acpm_tmu_set_init(&cap);
|
|
#else
|
|
register_pm_notifier(&exynos_tmu_pm_notifier);
|
|
#endif
|
|
cpuhp_setup_state_nocalls(CPUHP_EXYNOS_BOOST_CTRL_POST, "dtm_boost_cb",
|
|
exynos_tmu_boost_callback, exynos_tmu_boost_callback);
|
|
}
|
|
|
|
if (data->hotplug_enable)
|
|
mutex_init(&data->hotplug_lock);
|
|
|
|
if (!IS_ERR(data->tzd))
|
|
data->tzd->ops->set_mode(data->tzd, THERMAL_DEVICE_ENABLED);
|
|
|
|
#ifdef CONFIG_MALI_DEBUG_KERNEL_SYSFS
|
|
if (data->id == EXYNOS_GPU_THERMAL_ZONE_ID)
|
|
gpu_thermal_data = data;
|
|
#endif
|
|
|
|
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;
|
|
|
|
#ifndef CONFIG_EXYNOS_ACPM_THERMAL
|
|
if (list_is_singular(&dtm_dev_list))
|
|
unregister_pm_notifier(&exynos_tmu_pm_notifier);
|
|
#endif
|
|
|
|
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);
|
|
num_of_devices--;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int exynos_tmu_suspend(struct device *dev)
|
|
{
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
|
|
suspended_count++;
|
|
disable_irq(data->irq);
|
|
|
|
#ifdef CONFIG_SND_SOC_SAMSUNG_ABOX
|
|
#if defined(CONFIG_SOC_EXYNOS9810)
|
|
cp_call_mode = is_aud_on() && cap.acpm_irq;
|
|
#elif defined(CONFIG_SOC_EXYNOS9610)
|
|
cp_call_mode = abox_is_on() && cap.acpm_irq;
|
|
#endif
|
|
#endif
|
|
|
|
if (cp_call_mode) {
|
|
if (suspended_count == num_of_devices) {
|
|
exynos_acpm_tmu_set_cp_call();
|
|
pr_info("%s: TMU suspend w/ AUD-on complete\n", __func__);
|
|
}
|
|
} else {
|
|
exynos_tmu_control(pdev, false);
|
|
if (suspended_count == num_of_devices) {
|
|
exynos_acpm_tmu_set_suspend();
|
|
pr_info("%s: TMU suspend complete\n", __func__);
|
|
}
|
|
}
|
|
#else
|
|
exynos_tmu_control(to_platform_device(dev), false);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_tmu_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
|
int temp, stat;
|
|
|
|
if (suspended_count == num_of_devices)
|
|
exynos_acpm_tmu_set_resume();
|
|
|
|
if (!cp_call_mode) {
|
|
exynos_tmu_initialize(pdev);
|
|
exynos_tmu_control(pdev, true);
|
|
}
|
|
|
|
exynos_acpm_tmu_set_read_temp(data->tzd->id, &temp, &stat);
|
|
|
|
enable_irq(data->irq);
|
|
suspended_count--;
|
|
|
|
if (!suspended_count)
|
|
pr_info("%s: TMU resume complete\n", __func__);
|
|
#else
|
|
exynos_tmu_initialize(pdev);
|
|
exynos_tmu_control(pdev, true);
|
|
#endif
|
|
|
|
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,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
.probe = exynos_tmu_probe,
|
|
.remove = exynos_tmu_remove,
|
|
};
|
|
|
|
module_platform_driver(exynos_tmu_driver);
|
|
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
static void exynos_acpm_tmu_test_cp_call(bool mode)
|
|
{
|
|
struct exynos_tmu_data *devnode;
|
|
|
|
if (mode) {
|
|
list_for_each_entry(devnode, &dtm_dev_list, node) {
|
|
disable_irq(devnode->irq);
|
|
}
|
|
exynos_acpm_tmu_set_cp_call();
|
|
} else {
|
|
exynos_acpm_tmu_set_resume();
|
|
list_for_each_entry(devnode, &dtm_dev_list, node) {
|
|
enable_irq(devnode->irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int emul_call_get(void *data, unsigned long long *val)
|
|
{
|
|
*val = exynos_acpm_tmu_is_test_mode();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emul_call_set(void *data, unsigned long long val)
|
|
{
|
|
int status = exynos_acpm_tmu_is_test_mode();
|
|
|
|
if ((val == 0 || val == 1) && (val != status)) {
|
|
exynos_acpm_tmu_set_test_mode(val);
|
|
exynos_acpm_tmu_test_cp_call(val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(emul_call_fops, emul_call_get, emul_call_set, "%llu\n");
|
|
|
|
static int log_print_set(void *data, unsigned long long val)
|
|
{
|
|
if (val == 0 || val == 1)
|
|
exynos_acpm_tmu_log(val);
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(log_print_fops, NULL, log_print_set, "%llu\n");
|
|
|
|
static ssize_t ipc_dump1_read(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
union {
|
|
unsigned int dump[2];
|
|
unsigned char val[8];
|
|
} data;
|
|
char buf[48];
|
|
ssize_t ret;
|
|
|
|
exynos_acpm_tmu_ipc_dump(0, data.dump);
|
|
|
|
ret = snprintf(buf, sizeof(buf), "%3d %3d %3d %3d %3d %3d %3d\n",
|
|
data.val[1], data.val[2], data.val[3],
|
|
data.val[4], data.val[5], data.val[6], data.val[7]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, ret);
|
|
}
|
|
|
|
static ssize_t ipc_dump2_read(struct file *file, char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
union {
|
|
unsigned int dump[2];
|
|
unsigned char val[8];
|
|
} data;
|
|
char buf[48];
|
|
ssize_t ret;
|
|
|
|
exynos_acpm_tmu_ipc_dump(EXYNOS_GPU_THERMAL_ZONE_ID, data.dump);
|
|
|
|
ret = snprintf(buf, sizeof(buf), "%3d %3d %3d %3d %3d %3d %3d\n",
|
|
data.val[1], data.val[2], data.val[3],
|
|
data.val[4], data.val[5], data.val[6], data.val[7]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, ret);
|
|
|
|
}
|
|
|
|
static const struct file_operations ipc_dump1_fops = {
|
|
.open = simple_open,
|
|
.read = ipc_dump1_read,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static const struct file_operations ipc_dump2_fops = {
|
|
.open = simple_open,
|
|
.read = ipc_dump2_read,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
#endif
|
|
|
|
static struct dentry *debugfs_root;
|
|
|
|
static int exynos_thermal_create_debugfs(void)
|
|
{
|
|
debugfs_root = debugfs_create_dir("exynos-thermal", NULL);
|
|
if (!debugfs_root) {
|
|
pr_err("Failed to create exynos thermal debugfs\n");
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
|
|
debugfs_create_file("emul_call", 0644, debugfs_root, NULL, &emul_call_fops);
|
|
debugfs_create_file("log_print", 0644, debugfs_root, NULL, &log_print_fops);
|
|
debugfs_create_file("ipc_dump1", 0644, debugfs_root, NULL, &ipc_dump1_fops);
|
|
debugfs_create_file("ipc_dump2", 0644, debugfs_root, NULL, &ipc_dump2_fops);
|
|
#endif
|
|
return 0;
|
|
}
|
|
arch_initcall(exynos_thermal_create_debugfs);
|
|
|
|
#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, id_max = 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[data->id]);
|
|
temp[data->id] /= 1000;
|
|
|
|
if (id_max < data->id)
|
|
id_max = data->id;
|
|
} else {
|
|
pr_err("%s: id:%d %s\n", __func__, data->id,
|
|
data->tmu_name);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i <= id_max; i++)
|
|
ret += sprintf(buf + ret, "%d,", temp[i]);
|
|
|
|
sprintf(buf + ret - 1, "\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR(curr_temp, 0444, 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 */
|
|
|
|
MODULE_DESCRIPTION("EXYNOS TMU Driver");
|
|
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:exynos-tmu");
|