blob: 3b57abe7d221e895fc649d2d530df9cea1acc433 [file] [log] [blame]
/*
* 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>
#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 */
#if defined(CONFIG_SOC_EXYNOS9810)
/* Exynos9810 */
#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_INTSTAT 0x74
#define EXYNOS_TMU_REG_INTCLEAR 0x78
#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_TMU_REG_TRIM0 (0x3C)
#define EXYNOS_TMU_REG_INTPEND0 (0x118)
#define EXYNOS_TMU_REG_INTPEND5 (0x318)
#define EXYNOS_TMU_REG_INTPEND8 (0x658)
#define EXYNOS_TMU_REG_THD(p) ((p == 0) ? \
EXYNOS_TMU_REG_THD_TEMP0 : \
((p < 8) ? \
(EXYNOS_TMU_REG_THD_TEMP1 + (p - 1) * 0x20) : \
(EXYNOS_TMU_REG_THD_TEMP8 + (p - 8) * 0x20)))
#define EXYNOS_TMU_REG_INTEN(p) ((p < 5) ? \
(EXYNOS_TMU_REG_INTEN0 + p * 0x10) : \
((p < 8) ? \
(EXYNOS_TMU_REG_INTEN5 + (p - 5) * 0x10) : \
(EXYNOS_TMU_REG_INTEN8 + (p - 8) * 0x10)))
#define EXYNOS_TMU_REG_INTPEND(p) ((p < 5) ? \
(EXYNOS_TMU_REG_INTPEND0 + p * 0x10) : \
((p < 8) ? \
(EXYNOS_TMU_REG_INTPEND5 + (p - 5) * 0x10) : \
(EXYNOS_TMU_REG_INTPEND8 + (p - 8) * 0x10)))
#define EXYNOS_TMU_REG_EMUL_CON (0x160)
#define EXYNOS_TMU_REG_AVG_CON (0x38)
#define EXYNOS_TMU_REG_COUNTER_VALUE0 (0x30)
#define EXYNOS_TMU_REG_COUNTER_VALUE1 (0x34)
#define EXYNOS_TMU_TEMP_SHIFT (9)
#define EXYNOS_GPU_THERMAL_ZONE_ID (2)
/* Exynos9810 */
#elif defined(CONFIG_SOC_EXYNOS9820)
/* Exynos9820 */
#define EXYNOS_TMU_REG_TRIMINFO(p) (((p) - 0) * 4)
#define EXYNOS_TMU_REG_CONTROL (0x50)
#define EXYNOS_TMU_REG_CONTROL1 (0x54)
#define EXYNOS_TMU_REG_STATUS (0x80)
#define EXYNOS_TMU_REG_CURRENT_TEMP1_0 (0x84)
#define EXYNOS_TMU_REG_THD_TEMP0 (0xD0)
#define EXYNOS_TMU_REG_INTEN0 (0xF0)
#define EXYNOS_TMU_REG_INTPEND0 (0xF8)
#define EXYNOS_TMU_REG_SENSOR_OFFSET (0x50)
#define EXYNOS_TMU_REG_THD(p) (EXYNOS_TMU_REG_THD_TEMP0 + p * EXYNOS_TMU_REG_SENSOR_OFFSET)
#define EXYNOS_TMU_REG_INTPEND(p) (EXYNOS_TMU_REG_INTPEND0 + p * EXYNOS_TMU_REG_SENSOR_OFFSET)
#define EXYNOS_TMU_REG_INTEN(p) (EXYNOS_TMU_REG_INTEN0 + p * EXYNOS_TMU_REG_SENSOR_OFFSET)
#define EXYNOS_TMU_REG_TRIM0 (0x5C)
#define EXYNOS_TMU_REG_EMUL_CON (0xB0)
#define EXYNOS_TMU_REG_AVG_CON (0x58)
#define EXYNOS_TMU_REG_COUNTER_VALUE0 (0x74)
#define EXYNOS_TMU_REG_COUNTER_VALUE1 (0x78)
#define EXYNOS_TMU_TEMP_SHIFT (16)
#define EXYNOS_GPU_THERMAL_ZONE_ID (3)
/* Exynos9820 */
#endif
#include "linux/mcu_ipc.h"
#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_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_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_T_TRIM0_SHIFT (18)
#define EXYNOS_TMU_T_TRIM0_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_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_EN_TEMP_SEN_OFF_SHIFT (0)
#define EXYNOS_TMU_EN_TEMP_SEN_OFF_MASK (0xffff)
#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_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 TOTAL_SENSORS 16
#define DEFAULT_BALANCE_OFFSET 20
#ifdef CONFIG_EXYNOS_ACPM_THERMAL
#if defined(CONFIG_SOC_EXYNOS9810)
#define PMUREG_AUD_STATUS 0x4004
#define PMUREG_AUD_STATUS_MASK 0xF
#elif defined(CONFIG_SOC_EXYNOS9820)
#define PMUREG_AUD_STATUS 0x1904
#define PMUREG_AUD_STATUS_MASK 0x1
#endif
static struct acpm_tmu_cap cap;
static unsigned int num_of_devices, suspended_count;
static bool cp_call_mode;
static bool is_aud_on(void)
{
unsigned int val;
exynos_pmu_read(PMUREG_AUD_STATUS, &val);
pr_info("%s AUD_STATUS %d\n", __func__, val);
return ((val & PMUREG_AUD_STATUS_MASK) == PMUREG_AUD_STATUS_MASK);
}
#ifdef CONFIG_MCU_IPC
#define CP_MBOX_NUM 3
#define CP_MBOX_MASK 1
#define CP_MBOX_SHIFT 25
static bool is_cp_net_conn(void)
{
unsigned int val;
val = mbox_extract_value(MCU_CP, CP_MBOX_NUM, CP_MBOX_MASK, CP_MBOX_SHIFT);
pr_info("%s CP mobx value %d\n", __func__, val);
return !!val;
}
#else
static bool is_cp_net_conn(void)
{
return false;
}
#endif
#else
static bool suspended;
static DEFINE_MUTEX (thermal_suspend_lock);
#endif
static bool is_cpu_hotplugged_out;
/* 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 exynos98X0_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;
reg_off += EXYNOS_TMU_REG_THD(sensor);
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;
offset = EXYNOS_TMU_REG_INTEN(i);
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;
offset = EXYNOS_TMU_REG_INTEN(i);
writel(0, data->base + offset);
}
}
static void exynos98X0_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);
}
#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, "MID", THERMAL_NAME_LENGTH)) {
exynos_get_temp(data, &temp[1]);
temp[1] /= 1000;
} else if (!strncasecmp(data->tmu_name, "LITTLE", THERMAL_NAME_LENGTH)) {
exynos_get_temp(data, &temp[2]);
temp[2] /= 1000;
} else if (!strncasecmp(data->tmu_name, "G3D", THERMAL_NAME_LENGTH)) {
exynos_get_temp(data, &temp[3]);
temp[3] /= 1000;
} else if (!strncasecmp(data->tmu_name, "ISP", THERMAL_NAME_LENGTH)) {
exynos_get_temp(data, &temp[4]);
temp[4] /= 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 exynos98X0_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)
static bool cpufreq_limited;
static struct pm_qos_request thermal_cpu_limit_request;
#endif
static int exynos98X0_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 defined(CONFIG_SOC_EXYNOS9810)
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;
}
}
#endif
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 exynos98X0_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;
pend_reg = EXYNOS_TMU_REG_INTPEND(i);
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;
}
#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,exynos9820-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,exynos9820-tmu"))
return SOC_ARCH_EXYNOS9820;
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:
case SOC_ARCH_EXYNOS9820:
data->tmu_initialize = exynos98X0_tmu_initialize;
data->tmu_control = exynos98X0_tmu_control;
data->tmu_read = exynos98X0_tmu_read;
data->tmu_set_emulation = exynos98X0_tmu_set_emulation;
data->tmu_clear_irqs = exynos98X0_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;
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.
*/
exynos_cpuhp_request("DTM", *cpu_possible_mask, 0);
is_cpu_hotplugged_out = false;
}
} 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);
}
}
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 defined(CONFIG_SOC_EXYNOS9810)
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);
}
#elif defined(CONFIG_SOC_EXYNOS9820)
reg_offset = (data->sensor_info[i].sensor_num / 2) * 4;
bit_offset = (data->sensor_info[i].sensor_num % 2) * EXYNOS_TMU_TEMP_SHIFT;
#endif
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, limited_frequency = 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);
limited_frequency = value;
}
if (hotplug_out_threshold != 0 && hotplug_in_threshold != 0) {
data->hotplug_out_threshold = hotplug_out_threshold;
data->hotplug_in_threshold = hotplug_in_threshold;
data->limited_frequency = limited_frequency;
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;
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;
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)
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 (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;
}
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 (!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);
int cp_state;
suspended_count++;
disable_irq(data->irq);
cp_call_mode = is_aud_on() && cap.acpm_irq;
if (cp_call_mode) {
if (suspended_count == num_of_devices) {
exynos_acpm_tmu_set_cp_call();
pr_info("%s: TMU suspend w/ AUD-on\n", __func__);
}
} else {
exynos_tmu_control(pdev, false);
if (suspended_count == num_of_devices) {
cp_state = is_cp_net_conn();
exynos_acpm_tmu_set_suspend(cp_state);
pr_info("%s: TMU suspend w/ cp_state %d\n", __func__, cp_state);
}
}
#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);
pr_info("%s: thermal zone %d temp %d stat %d\n",
__func__, 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");