2051 lines
56 KiB
C
2051 lines
56 KiB
C
/*
|
|
* Copyright (c) 2011-2013, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/io.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_data/tegra_thermal.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/tegra-soc.h>
|
|
|
|
#include "tegra_soctherm.h"
|
|
|
|
/* Min temp granularity specified as X in 2^X.
|
|
* -1: Hi precision option: 2^-1 = 0.5C
|
|
* 0: Lo precision option: 2^0 = 1.0C
|
|
* NB: We must use lower precision (0) due to cp_fuse corrections
|
|
*/
|
|
static const int precision; /* default 0 -> low precision */
|
|
#define LOWER_PRECISION_FOR_CONV(val) ((!precision) ? ((val)*2) : (val))
|
|
#define LOWER_PRECISION_FOR_TEMP(val) ((!precision) ? ((val)/2) : (val))
|
|
#define PRECISION_IS_LOWER() ((!precision))
|
|
#define PRECISION_TO_STR() ((!precision) ? "Lo" : "Hi")
|
|
|
|
#define TS_TSENSE_REGS_SIZE 0x20
|
|
#define TS_TSENSE_REG_OFFSET(reg, ts) ((reg) + ((ts) * TS_TSENSE_REGS_SIZE))
|
|
|
|
#define TS_THERM_LVL_REGS_SIZE 0x20
|
|
#define TS_THERM_GRP_REGS_SIZE 0x04
|
|
#define TS_THERM_REG_OFFSET(rg, lv, gr) ((rg) + ((lv) * TS_THERM_LVL_REGS_SIZE)\
|
|
+ ((gr) * TS_THERM_GRP_REGS_SIZE))
|
|
|
|
#define CTL_LVL0_CPU0 0x0
|
|
#define CTL_LVL0_CPU0_UP_THRESH_SHIFT 17
|
|
#define CTL_LVL0_CPU0_UP_THRESH_MASK 0xff
|
|
#define CTL_LVL0_CPU0_DN_THRESH_SHIFT 9
|
|
#define CTL_LVL0_CPU0_DN_THRESH_MASK 0xff
|
|
#define CTL_LVL0_CPU0_EN_SHIFT 8
|
|
#define CTL_LVL0_CPU0_EN_MASK 0x1
|
|
#define CTL_LVL0_CPU0_DEV_THROT_LIGHT 0x1
|
|
#define CTL_LVL0_CPU0_DEV_THROT_HEAVY 0x2
|
|
#define CTL_LVL0_CPU0_CPU_THROT_SHIFT 5
|
|
#define CTL_LVL0_CPU0_CPU_THROT_MASK 0x3
|
|
#define CTL_LVL0_CPU0_GPU_THROT_SHIFT 3
|
|
#define CTL_LVL0_CPU0_GPU_THROT_MASK 0x3
|
|
#define CTL_LVL0_CPU0_STATUS_SHIFT 0
|
|
#define CTL_LVL0_CPU0_STATUS_MASK 0x3
|
|
|
|
#define THERMTRIP 0x80
|
|
#define THERMTRIP_ANY_EN_SHIFT 28
|
|
#define THERMTRIP_ANY_EN_MASK 0x1
|
|
#define THERMTRIP_MEM_EN_SHIFT 27
|
|
#define THERMTRIP_MEM_EN_MASK 0x1
|
|
#define THERMTRIP_GPU_EN_SHIFT 26
|
|
#define THERMTRIP_GPU_EN_MASK 0x1
|
|
#define THERMTRIP_CPU_EN_SHIFT 25
|
|
#define THERMTRIP_CPU_EN_MASK 0x1
|
|
#define THERMTRIP_TSENSE_EN_SHIFT 24
|
|
#define THERMTRIP_TSENSE_EN_MASK 0x1
|
|
#define THERMTRIP_GPUMEM_THRESH_SHIFT 16
|
|
#define THERMTRIP_GPUMEM_THRESH_MASK 0xff
|
|
#define THERMTRIP_CPU_THRESH_SHIFT 8
|
|
#define THERMTRIP_CPU_THRESH_MASK 0xff
|
|
#define THERMTRIP_TSENSE_THRESH_SHIFT 0
|
|
#define THERMTRIP_TSENSE_THRESH_MASK 0xff
|
|
|
|
#define TS_CPU0_CONFIG0 0xc0
|
|
#define TS_CPU0_CONFIG0_TALL_SHIFT 8
|
|
#define TS_CPU0_CONFIG0_TALL_MASK 0xfffff
|
|
#define TS_CPU0_CONFIG0_TCALC_OVER_SHIFT 4
|
|
#define TS_CPU0_CONFIG0_TCALC_OVER_MASK 0x1
|
|
#define TS_CPU0_CONFIG0_OVER_SHIFT 3
|
|
#define TS_CPU0_CONFIG0_OVER_MASK 0x1
|
|
#define TS_CPU0_CONFIG0_CPTR_OVER_SHIFT 2
|
|
#define TS_CPU0_CONFIG0_CPTR_OVER_MASK 0x1
|
|
#define TS_CPU0_CONFIG0_STOP_SHIFT 0
|
|
#define TS_CPU0_CONFIG0_STOP_MASK 0x1
|
|
|
|
#define TS_CPU0_CONFIG1 0xc4
|
|
#define TS_CPU0_CONFIG1_EN_SHIFT 31
|
|
#define TS_CPU0_CONFIG1_EN_MASK 0x1
|
|
#define TS_CPU0_CONFIG1_TIDDQ_SHIFT 15
|
|
#define TS_CPU0_CONFIG1_TIDDQ_MASK 0x3f
|
|
#define TS_CPU0_CONFIG1_TEN_COUNT_SHIFT 24
|
|
#define TS_CPU0_CONFIG1_TEN_COUNT_MASK 0x3f
|
|
#define TS_CPU0_CONFIG1_TSAMPLE_SHIFT 0
|
|
#define TS_CPU0_CONFIG1_TSAMPLE_MASK 0x3ff
|
|
|
|
#define TS_CPU0_CONFIG2 0xc8
|
|
#define TS_CPU0_CONFIG2_THERM_A_SHIFT 16
|
|
#define TS_CPU0_CONFIG2_THERM_A_MASK 0xffff
|
|
#define TS_CPU0_CONFIG2_THERM_B_SHIFT 0
|
|
#define TS_CPU0_CONFIG2_THERM_B_MASK 0xffff
|
|
|
|
#define TS_CPU0_STATUS0 0xcc
|
|
#define TS_CPU0_STATUS0_VALID_SHIFT 31
|
|
#define TS_CPU0_STATUS0_VALID_MASK 0x1
|
|
#define TS_CPU0_STATUS0_CAPTURE_SHIFT 0
|
|
#define TS_CPU0_STATUS0_CAPTURE_MASK 0xffff
|
|
|
|
#define TS_CPU0_STATUS1 0xd0
|
|
#define TS_CPU0_STATUS1_TEMP_VALID_SHIFT 31
|
|
#define TS_CPU0_STATUS1_TEMP_VALID_MASK 0x1
|
|
#define TS_CPU0_STATUS1_TEMP_SHIFT 0
|
|
#define TS_CPU0_STATUS1_TEMP_MASK 0xffff
|
|
|
|
#define TS_CPU0_STATUS2 0xd4
|
|
|
|
#define TS_PDIV 0x1c0
|
|
#define TS_PDIV_CPU_SHIFT 12
|
|
#define TS_PDIV_CPU_MASK 0xf
|
|
#define TS_PDIV_GPU_SHIFT 8
|
|
#define TS_PDIV_GPU_MASK 0xf
|
|
#define TS_PDIV_MEM_SHIFT 4
|
|
#define TS_PDIV_MEM_MASK 0xf
|
|
#define TS_PDIV_PLLX_SHIFT 0
|
|
#define TS_PDIV_PLLX_MASK 0xf
|
|
|
|
#define TS_HOTSPOT_OFF 0x1c4
|
|
#define TS_HOTSPOT_OFF_CPU_SHIFT 16
|
|
#define TS_HOTSPOT_OFF_CPU_MASK 0xff
|
|
#define TS_HOTSPOT_OFF_GPU_SHIFT 8
|
|
#define TS_HOTSPOT_OFF_GPU_MASK 0xff
|
|
#define TS_HOTSPOT_OFF_MEM_SHIFT 0
|
|
#define TS_HOTSPOT_OFF_MEM_MASK 0xff
|
|
|
|
#define TS_TEMP1 0x1c8
|
|
#define TS_TEMP1_CPU_TEMP_SHIFT 16
|
|
#define TS_TEMP1_CPU_TEMP_MASK 0xffff
|
|
#define TS_TEMP1_GPU_TEMP_SHIFT 0
|
|
#define TS_TEMP1_GPU_TEMP_MASK 0xffff
|
|
|
|
#define TS_TEMP2 0x1cc
|
|
#define TS_TEMP2_MEM_TEMP_SHIFT 16
|
|
#define TS_TEMP2_MEM_TEMP_MASK 0xffff
|
|
#define TS_TEMP2_PLLX_TEMP_SHIFT 0
|
|
#define TS_TEMP2_PLLX_TEMP_MASK 0xffff
|
|
|
|
#define INTR_STATUS 0x84
|
|
#define INTR_EN 0x88
|
|
#define INTR_DIS 0x8c
|
|
|
|
#define INTR_POS_MD3_SHIFT 31
|
|
#define INTR_POS_MD3_MASK 0x1
|
|
#define INTR_POS_MU3_SHIFT 30
|
|
#define INTR_POS_MU3_MASK 0x1
|
|
#define INTR_POS_MD2_SHIFT 29
|
|
#define INTR_POS_MD2_MASK 0x1
|
|
#define INTR_POS_MU2_SHIFT 28
|
|
#define INTR_POS_MU2_MASK 0x1
|
|
#define INTR_POS_MD1_SHIFT 27
|
|
#define INTR_POS_MD1_MASK 0x1
|
|
#define INTR_POS_MU1_SHIFT 26
|
|
#define INTR_POS_MU1_MASK 0x1
|
|
#define INTR_POS_MD0_SHIFT 25
|
|
#define INTR_POS_MD0_MASK 0x1
|
|
#define INTR_POS_MU0_SHIFT 24
|
|
#define INTR_POS_MU0_MASK 0x1
|
|
#define INTR_POS_GD3_SHIFT 23
|
|
#define INTR_POS_GD3_MASK 0x1
|
|
#define INTR_POS_GU3_SHIFT 22
|
|
#define INTR_POS_GU3_MASK 0x1
|
|
#define INTR_POS_GD2_SHIFT 21
|
|
#define INTR_POS_GD2_MASK 0x1
|
|
#define INTR_POS_GU2_SHIFT 20
|
|
#define INTR_POS_GU2_MASK 0x1
|
|
#define INTR_POS_GD1_SHIFT 19
|
|
#define INTR_POS_GD1_MASK 0x1
|
|
#define INTR_POS_GU1_SHIFT 18
|
|
#define INTR_POS_GU1_MASK 0x1
|
|
#define INTR_POS_GD0_SHIFT 17
|
|
#define INTR_POS_GD0_MASK 0x1
|
|
#define INTR_POS_GU0_SHIFT 16
|
|
#define INTR_POS_GU0_MASK 0x1
|
|
#define INTR_POS_CD3_SHIFT 15
|
|
#define INTR_POS_CD3_MASK 0x1
|
|
#define INTR_POS_CU3_SHIFT 14
|
|
#define INTR_POS_CU3_MASK 0x1
|
|
#define INTR_POS_CD2_SHIFT 13
|
|
#define INTR_POS_CD2_MASK 0x1
|
|
#define INTR_POS_CU2_SHIFT 12
|
|
#define INTR_POS_CU2_MASK 0x1
|
|
#define INTR_POS_CD1_SHIFT 11
|
|
#define INTR_POS_CD1_MASK 0x1
|
|
#define INTR_POS_CU1_SHIFT 10
|
|
#define INTR_POS_CU1_MASK 0x1
|
|
#define INTR_POS_CD0_SHIFT 9
|
|
#define INTR_POS_CD0_MASK 0x1
|
|
#define INTR_POS_CU0_SHIFT 8
|
|
#define INTR_POS_CU0_MASK 0x1
|
|
#define INTR_POS_PD3_SHIFT 7
|
|
#define INTR_POS_PD3_MASK 0x1
|
|
#define INTR_POS_PU3_SHIFT 6
|
|
#define INTR_POS_PU3_MASK 0x1
|
|
#define INTR_POS_PD2_SHIFT 5
|
|
#define INTR_POS_PD2_MASK 0x1
|
|
#define INTR_POS_PU2_SHIFT 4
|
|
#define INTR_POS_PU2_MASK 0x1
|
|
#define INTR_POS_PD1_SHIFT 3
|
|
#define INTR_POS_PD1_MASK 0x1
|
|
#define INTR_POS_PU1_SHIFT 2
|
|
#define INTR_POS_PU1_MASK 0x1
|
|
#define INTR_POS_PD0_SHIFT 1
|
|
#define INTR_POS_PD0_MASK 0x1
|
|
#define INTR_POS_PU0_SHIFT 0
|
|
#define INTR_POS_PU0_MASK 0x1
|
|
|
|
|
|
#define UP_STATS_L0 0x10
|
|
#define DN_STATS_L0 0x14
|
|
|
|
#define STATS_CTL 0x94
|
|
#define STATS_CTL_CLR_DN 0x8
|
|
#define STATS_CTL_EN_DN 0x4
|
|
#define STATS_CTL_CLR_UP 0x2
|
|
#define STATS_CTL_EN_UP 0x1
|
|
|
|
#define THROT_GLOBAL_CFG 0x400
|
|
|
|
#define CPU_PSKIP_STATUS 0x418
|
|
#define CPU_PSKIP_STATUS_M_SHIFT 12
|
|
#define CPU_PSKIP_STATUS_M_MASK 0xff
|
|
#define CPU_PSKIP_STATUS_N_SHIFT 4
|
|
#define CPU_PSKIP_STATUS_N_MASK 0xff
|
|
#define CPU_PSKIP_STATUS_ENABLED_SHIFT 0
|
|
#define CPU_PSKIP_STATUS_ENABLED_MASK 0x1
|
|
|
|
#define THROT_PRIORITY_LOCK 0x424
|
|
#define THROT_PRIORITY_LOCK_PRIORITY_SHIFT 0
|
|
#define THROT_PRIORITY_LOCK_PRIORITY_MASK 0xff
|
|
|
|
#define THROT_STATUS 0x428
|
|
#define THROT_STATUS_BREACH_SHIFT 12
|
|
#define THROT_STATUS_BREACH_MASK 0x1
|
|
#define THROT_STATUS_STATE_SHIFT 4
|
|
#define THROT_STATUS_STATE_MASK 0xff
|
|
#define THROT_STATUS_ENABLED_SHIFT 0
|
|
#define THROT_STATUS_ENABLED_MASK 0x1
|
|
|
|
#define THROT_PSKIP_CTRL_LITE_CPU 0x430
|
|
#define THROT_PSKIP_CTRL_ENABLE_SHIFT 31
|
|
#define THROT_PSKIP_CTRL_ENABLE_MASK 0x1
|
|
#define THROT_PSKIP_CTRL_DIVIDEND_SHIFT 8
|
|
#define THROT_PSKIP_CTRL_DIVIDEND_MASK 0xff
|
|
#define THROT_PSKIP_CTRL_DIVISOR_SHIFT 0
|
|
#define THROT_PSKIP_CTRL_DIVISOR_MASK 0xff
|
|
|
|
#define THROT_PSKIP_RAMP_LITE_CPU 0x434
|
|
#define THROT_PSKIP_RAMP_DURATION_SHIFT 8
|
|
#define THROT_PSKIP_RAMP_DURATION_MASK 0xffff
|
|
#define THROT_PSKIP_RAMP_STEP_SHIFT 0
|
|
#define THROT_PSKIP_RAMP_STEP_MASK 0xff
|
|
|
|
#define THROT_LITE_PRIORITY 0x444
|
|
#define THROT_LITE_PRIORITY_PRIORITY_SHIFT 0
|
|
#define THROT_LITE_PRIORITY_PRIORITY_MASK 0xff
|
|
|
|
#define THROT_OFFSET 0x30
|
|
|
|
#define FUSE_BASE_CP_SHIFT 0
|
|
#define FUSE_BASE_CP_MASK 0x3ff
|
|
#define FUSE_BASE_FT_SHIFT 16
|
|
#define FUSE_BASE_FT_MASK 0x7ff
|
|
#define FUSE_SHIFT_CP_SHIFT 10
|
|
#define FUSE_SHIFT_CP_MASK 0x3f
|
|
#define FUSE_SHIFT_CP_BITS 6
|
|
#define FUSE_SHIFT_FT_SHIFT 27
|
|
#define FUSE_SHIFT_FT_MASK 0x1f
|
|
#define FUSE_SHIFT_FT_BITS 5
|
|
|
|
#define FUSE_TSENSOR_CALIB_FT_SHIFT 13
|
|
#define FUSE_TSENSOR_CALIB_FT_MASK 0x1fff
|
|
#define FUSE_TSENSOR_CALIB_CP_SHIFT 0
|
|
#define FUSE_TSENSOR_CALIB_CP_MASK 0x1fff
|
|
#define FUSE_TSENSOR_CALIB_BITS 13
|
|
|
|
/* car register offsets needed for enabling HW throttling */
|
|
#define CAR_SUPER_CCLKG_DIVIDER 0x36c
|
|
#define CDIVG_USE_THERM_CONTROLS_SHIFT 30
|
|
#define CDIVG_USE_THERM_CONTROLS_MASK 0x1
|
|
|
|
/* pmc register offsets needed for powering off PMU */
|
|
#define PMC_SCRATCH_WRITE_SHIFT 2
|
|
#define PMC_SCRATCH_WRITE_MASK 0x1
|
|
#define PMC_ENABLE_RST_SHIFT 1
|
|
#define PMC_ENABLE_RST_MASK 0x1
|
|
#define PMC_SENSOR_CTRL 0x1B0
|
|
#define PMC_SCRATCH54 0x258
|
|
#define PMC_SCRATCH55 0x25C
|
|
|
|
/* scratch54 register bit fields */
|
|
#define PMU_OFF_DATA_SHIFT 8
|
|
#define PMU_OFF_DATA_MASK 0xff
|
|
#define PMU_OFF_ADDR_SHIFT 0
|
|
#define PMU_OFF_ADDR_MASK 0xff
|
|
|
|
/* scratch55 register bit fields */
|
|
#define RESET_TEGRA_SHIFT 31
|
|
#define RESET_TEGRA_MASK 0x1
|
|
#define CONTROLLER_TYPE_SHIFT 30
|
|
#define CONTROLLER_TYPE_MASK 0x1
|
|
#define I2C_CONTROLLER_ID_SHIFT 27
|
|
#define I2C_CONTROLLER_ID_MASK 0x7
|
|
#define PINMUX_SHIFT 24
|
|
#define PINMUX_MASK 0x7
|
|
#define CHECKSUM_SHIFT 16
|
|
#define CHECKSUM_MASK 0xff
|
|
#define PMU_16BIT_SUPPORT_SHIFT 15
|
|
#define PMU_16BIT_SUPPORT_MASK 0x1
|
|
#define PMU_I2C_ADDRESS_SHIFT 0
|
|
#define PMU_I2C_ADDRESS_MASK 0x7f
|
|
|
|
#define THROT_PSKIP_CTRL(throt, dev) (THROT_PSKIP_CTRL_LITE_CPU + \
|
|
(THROT_OFFSET * throt) + \
|
|
(8 * dev))
|
|
#define THROT_PSKIP_RAMP(throt, dev) (THROT_PSKIP_RAMP_LITE_CPU + \
|
|
(THROT_OFFSET * throt) + \
|
|
(8 * dev))
|
|
|
|
#define REG_SET(r, _name, val) (((r) & ~(_name##_MASK << _name##_SHIFT)) | \
|
|
(((val) & _name##_MASK) << _name##_SHIFT))
|
|
#define REG_GET_BIT(r, _name) ((r) & (_name##_MASK << _name##_SHIFT))
|
|
#define REG_GET(r, _name) (REG_GET_BIT(r, _name) >> _name##_SHIFT)
|
|
#define MAKE_SIGNED32(val, nb) ((s32)(val) << (32 - (nb)) >> (32 - (nb)))
|
|
|
|
static DEFINE_MUTEX(soctherm_suspend_resume_lock);
|
|
|
|
static int soctherm_suspend(struct device *dev);
|
|
static int soctherm_resume(struct device *dev);
|
|
|
|
static struct soctherm_platform_data *plat_data;
|
|
|
|
static inline void soctherm_writel(u32 value, u32 reg)
|
|
{
|
|
__raw_writel(value, (plat_data->soctherm_base + reg));
|
|
}
|
|
|
|
static inline u32 soctherm_readl(u32 reg)
|
|
{
|
|
return __raw_readl(plat_data->soctherm_base + reg);
|
|
}
|
|
|
|
static inline void pmc_writel(u32 value, u32 reg)
|
|
{
|
|
__raw_writel(value, (plat_data->pmc_base + reg));
|
|
}
|
|
|
|
static inline u32 pmc_readl(u32 reg)
|
|
{
|
|
return __raw_readl(plat_data->pmc_base + reg);
|
|
}
|
|
|
|
static inline void clk_reset_writel(u32 value, u32 reg)
|
|
{
|
|
__raw_writel(value, (plat_data->clk_reset_base + reg));
|
|
}
|
|
|
|
static inline u32 clk_reset_readl(u32 reg)
|
|
{
|
|
return __raw_readl(plat_data->clk_reset_base + reg);
|
|
}
|
|
|
|
/* Convert raw count read from TSOSC to temperature, same as HW arithmetic */
|
|
static inline long temp_convert(int cap, int therm_a, int therm_b)
|
|
{
|
|
cap *= therm_a;
|
|
cap >>= 10;
|
|
cap += (therm_b << 3);
|
|
cap *= LOWER_PRECISION_FOR_CONV(500);
|
|
cap /= 8;
|
|
return cap;
|
|
}
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
static struct thermal_zone_device *thz[THERM_SIZE];
|
|
#endif
|
|
|
|
static u32 fuse_calib_base_cp;
|
|
static u32 fuse_calib_base_ft;
|
|
static s32 actual_temp_cp;
|
|
static s32 actual_temp_ft;
|
|
|
|
static const char *const therm_names[] = {
|
|
[THERM_CPU] = "CPU",
|
|
[THERM_MEM] = "MEM",
|
|
[THERM_GPU] = "GPU",
|
|
[THERM_PLL] = "PLL",
|
|
};
|
|
|
|
static const char *const sensor_names[] = {
|
|
[TSENSE_CPU0] = "cpu0",
|
|
[TSENSE_CPU1] = "cpu1",
|
|
[TSENSE_CPU2] = "cpu2",
|
|
[TSENSE_CPU3] = "cpu3",
|
|
[TSENSE_MEM0] = "mem0",
|
|
[TSENSE_MEM1] = "mem1",
|
|
[TSENSE_GPU] = "gpu0",
|
|
[TSENSE_PLLX] = "pllx",
|
|
};
|
|
|
|
static const int sensor2tsensorcalib[] = {
|
|
[TSENSE_CPU0] = 0,
|
|
[TSENSE_CPU1] = 1,
|
|
[TSENSE_CPU2] = 2,
|
|
[TSENSE_CPU3] = 3,
|
|
[TSENSE_MEM0] = 5,
|
|
[TSENSE_MEM1] = 6,
|
|
[TSENSE_GPU] = 4,
|
|
[TSENSE_PLLX] = 7,
|
|
};
|
|
|
|
static const int tsensor2therm_map[] = {
|
|
[TSENSE_CPU0] = THERM_CPU,
|
|
[TSENSE_CPU1] = THERM_CPU,
|
|
[TSENSE_CPU2] = THERM_CPU,
|
|
[TSENSE_CPU3] = THERM_CPU,
|
|
[TSENSE_GPU] = THERM_GPU,
|
|
[TSENSE_MEM0] = THERM_MEM,
|
|
[TSENSE_MEM1] = THERM_MEM,
|
|
[TSENSE_PLLX] = THERM_PLL,
|
|
};
|
|
|
|
static const enum soctherm_throttle_dev_id therm2dev[] = {
|
|
[THERM_CPU] = THROTTLE_DEV_CPU,
|
|
[THERM_MEM] = THROTTLE_DEV_NONE,
|
|
[THERM_GPU] = THROTTLE_DEV_GPU,
|
|
[THERM_PLL] = THROTTLE_DEV_NONE,
|
|
};
|
|
|
|
static int sensor2therm_a[TSENSE_SIZE];
|
|
static int sensor2therm_b[TSENSE_SIZE];
|
|
|
|
static const struct soctherm_throttle_dev throttle_defaults[] = {
|
|
[THROTTLE_LIGHT] = {
|
|
.dividend = 229, /* 20% throttling */
|
|
.divisor = 255,
|
|
.duration = 0xff,
|
|
.step = 0xf,
|
|
},
|
|
[THROTTLE_HEAVY] = {
|
|
.dividend = 51, /* 80% throttling */
|
|
.divisor = 255,
|
|
.duration = 0xff,
|
|
.step = 0xf,
|
|
},
|
|
};
|
|
|
|
static inline bool is_thermal_dev_throttle_enabled(
|
|
const struct soctherm_platform_data *pdata,
|
|
size_t throttle_id,
|
|
enum soctherm_therm_id therm_id)
|
|
{
|
|
size_t idx;
|
|
|
|
if ((throttle_id >= ARRAY_SIZE(pdata->throttle)) ||
|
|
(therm_id >= ARRAY_SIZE(therm2dev)))
|
|
return false;
|
|
|
|
idx = therm2dev[therm_id];
|
|
if (idx == THROTTLE_DEV_NONE)
|
|
return false;
|
|
|
|
return pdata->throttle[throttle_id].devs[idx].enable;
|
|
}
|
|
|
|
static inline s64 div64_s64_precise(s64 a, s32 b)
|
|
{
|
|
s64 r, al;
|
|
|
|
/* scale up for increased precision in division */
|
|
al = a << 16;
|
|
|
|
r = div64_s64((al * 2) + 1, 2 * b);
|
|
return r >> 16;
|
|
}
|
|
|
|
static inline long temp_translate(int readback)
|
|
{
|
|
int abs = readback >> 8;
|
|
int lsb = (readback & 0x80) >> 7;
|
|
int sign = readback & 0x01 ? -1 : 1;
|
|
|
|
return (abs * LOWER_PRECISION_FOR_CONV(1000) +
|
|
lsb * LOWER_PRECISION_FOR_CONV(500)) * sign;
|
|
}
|
|
|
|
#ifdef CONFIG_THERMAL
|
|
static inline void prog_hw_shutdown(struct thermal_trip_info *trip_state,
|
|
int therm)
|
|
{
|
|
int trip_temp;
|
|
u32 r;
|
|
|
|
trip_temp = LOWER_PRECISION_FOR_TEMP(trip_state->trip_temp / 1000);
|
|
|
|
r = soctherm_readl(THERMTRIP);
|
|
if (therm == THERM_CPU) {
|
|
r = REG_SET(r, THERMTRIP_CPU_EN, 1);
|
|
r = REG_SET(r, THERMTRIP_CPU_THRESH, trip_temp);
|
|
} else if (therm == THERM_GPU) {
|
|
r = REG_SET(r, THERMTRIP_GPU_EN, 1);
|
|
r = REG_SET(r, THERMTRIP_GPUMEM_THRESH, trip_temp);
|
|
} else if (therm == THERM_PLL) {
|
|
r = REG_SET(r, THERMTRIP_TSENSE_EN, 1);
|
|
r = REG_SET(r, THERMTRIP_TSENSE_THRESH, trip_temp);
|
|
} else if (therm == THERM_MEM) {
|
|
r = REG_SET(r, THERMTRIP_MEM_EN, 1);
|
|
r = REG_SET(r, THERMTRIP_GPUMEM_THRESH, trip_temp);
|
|
}
|
|
r = REG_SET(r, THERMTRIP_ANY_EN, 0);
|
|
soctherm_writel(r, THERMTRIP);
|
|
}
|
|
|
|
static inline void prog_hw_threshold(struct thermal_trip_info *trip_state,
|
|
int therm, int throt)
|
|
{
|
|
int trip_temp;
|
|
u32 r, reg_off;
|
|
|
|
trip_temp = LOWER_PRECISION_FOR_TEMP(trip_state->trip_temp / 1000);
|
|
|
|
/* Hardcode LITE on level-1 and HEAVY on level-2 */
|
|
reg_off = TS_THERM_REG_OFFSET(CTL_LVL0_CPU0, throt + 1, therm);
|
|
|
|
r = soctherm_readl(reg_off);
|
|
r = REG_SET(r, CTL_LVL0_CPU0_UP_THRESH, trip_temp);
|
|
r = REG_SET(r, CTL_LVL0_CPU0_DN_THRESH, trip_temp);
|
|
r = REG_SET(r, CTL_LVL0_CPU0_EN, 1);
|
|
r = REG_SET(r, CTL_LVL0_CPU0_CPU_THROT,
|
|
throt == THROTTLE_HEAVY ?
|
|
CTL_LVL0_CPU0_DEV_THROT_HEAVY :
|
|
CTL_LVL0_CPU0_DEV_THROT_LIGHT);
|
|
r = REG_SET(r, CTL_LVL0_CPU0_GPU_THROT,
|
|
throt == THROTTLE_HEAVY ?
|
|
CTL_LVL0_CPU0_DEV_THROT_HEAVY :
|
|
CTL_LVL0_CPU0_DEV_THROT_LIGHT);
|
|
|
|
soctherm_writel(r, reg_off);
|
|
}
|
|
|
|
static int soctherm_set_limits(enum soctherm_therm_id therm,
|
|
long lo_limit, long hi_limit)
|
|
{
|
|
u32 r, reg_off;
|
|
|
|
reg_off = TS_THERM_REG_OFFSET(CTL_LVL0_CPU0, 0, therm);
|
|
r = soctherm_readl(reg_off);
|
|
|
|
lo_limit = LOWER_PRECISION_FOR_TEMP(lo_limit);
|
|
hi_limit = LOWER_PRECISION_FOR_TEMP(hi_limit);
|
|
|
|
r = REG_SET(r, CTL_LVL0_CPU0_DN_THRESH, lo_limit);
|
|
r = REG_SET(r, CTL_LVL0_CPU0_UP_THRESH, hi_limit);
|
|
r = REG_SET(r, CTL_LVL0_CPU0_EN, 1);
|
|
soctherm_writel(r, reg_off);
|
|
|
|
r = soctherm_readl(INTR_EN);
|
|
if (therm == THERM_CPU) {
|
|
r = REG_SET(r, INTR_POS_CD0, 1);
|
|
r = REG_SET(r, INTR_POS_CU0, 1);
|
|
} else if (therm == THERM_GPU) {
|
|
r = REG_SET(r, INTR_POS_GD0, 1);
|
|
r = REG_SET(r, INTR_POS_GU0, 1);
|
|
}
|
|
soctherm_writel(r, INTR_EN);
|
|
return 0;
|
|
}
|
|
|
|
static void soctherm_update_zone(int zn)
|
|
{
|
|
const int MAX_HIGH_TEMP = 128000;
|
|
long low_temp = 0, high_temp = MAX_HIGH_TEMP;
|
|
long trip_temp, passive_low_temp = MAX_HIGH_TEMP, zone_temp;
|
|
enum thermal_trip_type trip_type;
|
|
struct thermal_trip_info *trip_state;
|
|
struct thermal_zone_device *cur_thz = thz[zn];
|
|
int count, trips;
|
|
|
|
thermal_zone_device_update(cur_thz);
|
|
|
|
trips = cur_thz->trips;
|
|
for (count = 0; count < trips; count++) {
|
|
cur_thz->ops->get_trip_type(cur_thz, count, &trip_type);
|
|
if ((trip_type == THERMAL_TRIP_HOT) ||
|
|
(trip_type == THERMAL_TRIP_CRITICAL))
|
|
continue; /* handled in HW */
|
|
|
|
cur_thz->ops->get_trip_temp(cur_thz, count, &trip_temp);
|
|
|
|
trip_state = &plat_data->therm[zn].trips[count];
|
|
zone_temp = cur_thz->temperature;
|
|
|
|
if (!trip_state->tripped) { /* not tripped? update high */
|
|
if (trip_temp < high_temp)
|
|
high_temp = trip_temp;
|
|
} else { /* tripped? update low */
|
|
if (trip_type != THERMAL_TRIP_PASSIVE) {
|
|
/* get highest ACTIVE */
|
|
if (trip_temp > low_temp)
|
|
low_temp = trip_temp;
|
|
} else {
|
|
/* get lowest PASSIVE */
|
|
if (trip_temp < passive_low_temp)
|
|
passive_low_temp = trip_temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (passive_low_temp != MAX_HIGH_TEMP)
|
|
low_temp = max(low_temp, passive_low_temp);
|
|
|
|
soctherm_set_limits(zn, low_temp/1000, high_temp/1000);
|
|
}
|
|
|
|
static void soctherm_update(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < THERM_SIZE; i++) {
|
|
if (thz[i] && thz[i]->trips)
|
|
soctherm_update_zone(i);
|
|
}
|
|
}
|
|
|
|
static int soctherm_hw_action_get_max_state(struct thermal_cooling_device *cdev,
|
|
unsigned long *max_state)
|
|
{
|
|
struct thermal_trip_info *trip_state = cdev->devdata;
|
|
|
|
if (!trip_state)
|
|
return 0;
|
|
|
|
*max_state = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_hw_action_get_cur_state(struct thermal_cooling_device *cdev,
|
|
unsigned long *cur_state)
|
|
{
|
|
struct thermal_trip_info *trip_state = cdev->devdata;
|
|
u32 pskip_m;
|
|
|
|
if (!trip_state)
|
|
return 0;
|
|
|
|
*cur_state = 0;
|
|
if (trip_state->trip_type != THERMAL_TRIP_HOT)
|
|
return 0;
|
|
|
|
pskip_m = REG_GET(soctherm_readl(CPU_PSKIP_STATUS), CPU_PSKIP_STATUS_M);
|
|
|
|
if (strnstr(trip_state->cdev_type, "heavy", THERMAL_NAME_LENGTH) &&
|
|
pskip_m == throttle_defaults[THROTTLE_LIGHT].dividend)
|
|
return 0;
|
|
|
|
if (strnstr(trip_state->cdev_type, "light", THERMAL_NAME_LENGTH) &&
|
|
pskip_m == throttle_defaults[THROTTLE_HEAVY].dividend)
|
|
return 0;
|
|
|
|
*cur_state =
|
|
!!REG_GET(soctherm_readl(THROT_STATUS), THROT_STATUS_STATE);
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_hw_action_set_cur_state(struct thermal_cooling_device *cdev,
|
|
unsigned long cur_state)
|
|
{
|
|
return 0; /* hw sets this state */
|
|
}
|
|
|
|
static struct thermal_cooling_device_ops soctherm_hw_action_ops = {
|
|
.get_max_state = soctherm_hw_action_get_max_state,
|
|
.get_cur_state = soctherm_hw_action_get_cur_state,
|
|
.set_cur_state = soctherm_hw_action_set_cur_state,
|
|
};
|
|
|
|
static int soctherm_bind(struct thermal_zone_device *thz,
|
|
struct thermal_cooling_device *cdev)
|
|
{
|
|
int i, index = ((int)thz->devdata) - TSENSE_SIZE;
|
|
struct thermal_trip_info *trip_state;
|
|
|
|
if (index < 0)
|
|
return 0;
|
|
|
|
for (i = 0; i < plat_data->therm[index].num_trips; i++) {
|
|
trip_state = &plat_data->therm[index].trips[i];
|
|
if (trip_state->bound)
|
|
continue;
|
|
if (trip_state->cdev_type &&
|
|
!strncmp(trip_state->cdev_type, cdev->type,
|
|
THERMAL_NAME_LENGTH)) {
|
|
thermal_zone_bind_cooling_device(thz, i, cdev,
|
|
trip_state->upper,
|
|
trip_state->lower);
|
|
trip_state->bound = true;
|
|
}
|
|
}
|
|
|
|
thermal_zone_device_update(thz);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_unbind(struct thermal_zone_device *thz,
|
|
struct thermal_cooling_device *cdev)
|
|
{
|
|
int i, index = ((int)thz->devdata) - TSENSE_SIZE;
|
|
struct thermal_trip_info *trip_state;
|
|
|
|
if (index < 0)
|
|
return 0;
|
|
|
|
for (i = 0; i < plat_data->therm[index].num_trips; i++) {
|
|
trip_state = &plat_data->therm[index].trips[i];
|
|
if (!trip_state->bound)
|
|
continue;
|
|
if (trip_state->cdev_type &&
|
|
!strncmp(trip_state->cdev_type, cdev->type,
|
|
THERMAL_NAME_LENGTH)) {
|
|
thermal_zone_unbind_cooling_device(thz, 0, cdev);
|
|
trip_state->bound = false;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_get_temp(struct thermal_zone_device *thz,
|
|
unsigned long *temp)
|
|
{
|
|
int index = (int)thz->devdata;
|
|
u32 r, regv, shft, mask;
|
|
enum soctherm_sense i, j;
|
|
int tt, ti;
|
|
|
|
if (index < TSENSE_SIZE) { /* 'TSENSE_XXX' thermal zone */
|
|
regv = TS_CPU0_STATUS1;
|
|
shft = TS_CPU0_STATUS1_TEMP_SHIFT;
|
|
mask = TS_CPU0_STATUS1_TEMP_MASK;
|
|
i = index;
|
|
j = index;
|
|
} else {
|
|
index -= TSENSE_SIZE; /* 'THERM_XXX' thermal zone */
|
|
|
|
switch (index) {
|
|
case THERM_CPU:
|
|
regv = TS_TEMP1;
|
|
shft = TS_TEMP1_CPU_TEMP_SHIFT;
|
|
mask = TS_TEMP1_CPU_TEMP_MASK;
|
|
i = TSENSE_CPU0;
|
|
j = TSENSE_CPU3;
|
|
break;
|
|
|
|
case THERM_GPU:
|
|
regv = TS_TEMP1;
|
|
shft = TS_TEMP1_GPU_TEMP_SHIFT;
|
|
mask = TS_TEMP1_GPU_TEMP_MASK;
|
|
i = TSENSE_GPU;
|
|
j = TSENSE_GPU;
|
|
break;
|
|
|
|
case THERM_PLL:
|
|
regv = TS_TEMP2;
|
|
shft = TS_TEMP2_PLLX_TEMP_SHIFT;
|
|
mask = TS_TEMP2_PLLX_TEMP_MASK;
|
|
i = TSENSE_PLLX;
|
|
j = TSENSE_PLLX;
|
|
break;
|
|
|
|
case THERM_MEM:
|
|
regv = TS_TEMP2;
|
|
shft = TS_TEMP2_MEM_TEMP_SHIFT;
|
|
mask = TS_TEMP2_MEM_TEMP_MASK;
|
|
i = TSENSE_MEM0;
|
|
j = TSENSE_MEM1;
|
|
break;
|
|
|
|
default:
|
|
return 0; /* error really */
|
|
}
|
|
}
|
|
|
|
if (plat_data->read_hw_temp) {
|
|
r = soctherm_readl(regv);
|
|
*temp = temp_translate((r & (mask << shft)) >> shft);
|
|
} else {
|
|
for (tt = 0; i <= j; i++) {
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(
|
|
TS_CPU0_STATUS0, i));
|
|
ti = temp_convert(REG_GET(r, TS_CPU0_STATUS0_CAPTURE),
|
|
sensor2therm_a[i],
|
|
sensor2therm_b[i]);
|
|
*temp = tt = max(tt, ti);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_get_trip_type(struct thermal_zone_device *thz,
|
|
int trip, enum thermal_trip_type *type)
|
|
{
|
|
int index = ((int)thz->devdata) - TSENSE_SIZE;
|
|
struct thermal_trip_info *trip_state;
|
|
|
|
if (index < 0)
|
|
return -EINVAL;
|
|
|
|
trip_state = &plat_data->therm[index].trips[trip];
|
|
*type = trip_state->trip_type;
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_get_trip_temp(struct thermal_zone_device *thz,
|
|
int trip, unsigned long *temp)
|
|
{
|
|
int index = ((int)thz->devdata) - TSENSE_SIZE;
|
|
struct thermal_trip_info *trip_state;
|
|
unsigned long trip_temp, zone_temp;
|
|
|
|
if (index < 0)
|
|
return -EINVAL;
|
|
|
|
trip_state = &plat_data->therm[index].trips[trip];
|
|
trip_temp = trip_state->trip_temp;
|
|
zone_temp = thz->temperature;
|
|
|
|
if (zone_temp >= trip_temp) {
|
|
trip_temp -= trip_state->hysteresis;
|
|
trip_state->tripped = true;
|
|
} else if (trip_state->tripped) {
|
|
trip_temp -= trip_state->hysteresis;
|
|
if (zone_temp < trip_temp)
|
|
trip_state->tripped = false;
|
|
}
|
|
|
|
*temp = trip_temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_set_trip_temp(struct thermal_zone_device *thz,
|
|
int trip, unsigned long temp)
|
|
{
|
|
int index = ((int)thz->devdata) - TSENSE_SIZE;
|
|
struct thermal_trip_info *trip_state;
|
|
long rem;
|
|
|
|
if (index < 0)
|
|
return -EINVAL;
|
|
|
|
trip_state = &plat_data->therm[index].trips[trip];
|
|
|
|
trip_state->trip_temp = temp;
|
|
|
|
rem = trip_state->trip_temp % LOWER_PRECISION_FOR_CONV(1000);
|
|
if (rem) {
|
|
pr_warn("soctherm: zone%d/trip_point%d %ld mC rounded down\n",
|
|
index, trip, trip_state->trip_temp);
|
|
trip_state->trip_temp -= rem;
|
|
}
|
|
|
|
if (trip_state->trip_type == THERMAL_TRIP_HOT) {
|
|
if (strnstr(trip_state->cdev_type,
|
|
"heavy", THERMAL_NAME_LENGTH))
|
|
prog_hw_threshold(trip_state, index, THROTTLE_HEAVY);
|
|
else if (strnstr(trip_state->cdev_type,
|
|
"light", THERMAL_NAME_LENGTH))
|
|
prog_hw_threshold(trip_state, index, THROTTLE_LIGHT);
|
|
}
|
|
|
|
/* Allow SW to shutdown at 'Critical temperature reached' */
|
|
soctherm_update_zone(index);
|
|
|
|
/* Reprogram HW thermtrip */
|
|
if (trip_state->trip_type == THERMAL_TRIP_CRITICAL)
|
|
prog_hw_shutdown(trip_state, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_get_crit_temp(struct thermal_zone_device *thz,
|
|
unsigned long *temp)
|
|
{
|
|
int i, index = ((int)thz->devdata) - TSENSE_SIZE;
|
|
struct thermal_trip_info *trip_state;
|
|
|
|
if (index < 0)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < plat_data->therm[index].num_trips; i++) {
|
|
trip_state = &plat_data->therm[index].trips[i];
|
|
if (trip_state->trip_type != THERMAL_TRIP_CRITICAL)
|
|
continue;
|
|
*temp = trip_state->trip_temp;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int soctherm_get_trend(struct thermal_zone_device *thz,
|
|
int trip,
|
|
enum thermal_trend *trend)
|
|
{
|
|
int index = ((int)thz->devdata) - TSENSE_SIZE;
|
|
struct thermal_trip_info *trip_state;
|
|
long trip_temp;
|
|
|
|
if (index < 0)
|
|
return -EINVAL;
|
|
|
|
trip_state = &plat_data->therm[index].trips[trip];
|
|
thz->ops->get_trip_temp(thz, trip, &trip_temp);
|
|
|
|
switch (trip_state->trip_type) {
|
|
case THERMAL_TRIP_ACTIVE:
|
|
/* aggressive active cooling */
|
|
*trend = THERMAL_TREND_RAISING;
|
|
break;
|
|
case THERMAL_TRIP_PASSIVE:
|
|
if (thz->temperature > trip_state->trip_temp)
|
|
*trend = THERMAL_TREND_RAISING;
|
|
else if (thz->temperature < trip_temp)
|
|
*trend = THERMAL_TREND_DROPPING;
|
|
else
|
|
*trend = THERMAL_TREND_STABLE;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct thermal_zone_device_ops soctherm_ops = {
|
|
.bind = soctherm_bind,
|
|
.unbind = soctherm_unbind,
|
|
.get_temp = soctherm_get_temp,
|
|
.get_trip_type = soctherm_get_trip_type,
|
|
.get_trip_temp = soctherm_get_trip_temp,
|
|
.set_trip_temp = soctherm_set_trip_temp,
|
|
.get_crit_temp = soctherm_get_crit_temp,
|
|
.get_trend = soctherm_get_trend,
|
|
};
|
|
|
|
static void soctherm_create_cooling_device(struct soctherm_therm *therm,
|
|
int therm_id,
|
|
int trip_index)
|
|
{
|
|
int k;
|
|
char *type;
|
|
|
|
type = therm->trips[trip_index].cdev_type;
|
|
|
|
switch (therm->trips[trip_index].trip_type) {
|
|
case THERMAL_TRIP_CRITICAL:
|
|
thermal_cooling_device_register(type,
|
|
&therm->trips[trip_index],
|
|
&soctherm_hw_action_ops);
|
|
break;
|
|
|
|
case THERMAL_TRIP_HOT:
|
|
for (k = 0; k < THROTTLE_SIZE; k++) {
|
|
bool enabled;
|
|
|
|
enabled = is_thermal_dev_throttle_enabled(plat_data,
|
|
k,
|
|
therm_id);
|
|
if (!enabled)
|
|
continue;
|
|
|
|
if ((strnstr(type, "heavy", THERMAL_NAME_LENGTH) &&
|
|
k == THROTTLE_LIGHT) ||
|
|
(strnstr(type, "light", THERMAL_NAME_LENGTH) &&
|
|
k == THROTTLE_HEAVY))
|
|
continue;
|
|
|
|
thermal_cooling_device_register(
|
|
type,
|
|
&therm->trips[trip_index],
|
|
&soctherm_hw_action_ops);
|
|
}
|
|
break;
|
|
case THERMAL_TRIP_PASSIVE:
|
|
case THERMAL_TRIP_ACTIVE:
|
|
break; /* done elsewhere */
|
|
}
|
|
}
|
|
|
|
static int soctherm_thermal_sys_init(struct platform_device *pdev)
|
|
{
|
|
char name[THERMAL_NAME_LENGTH];
|
|
struct soctherm_therm *therm;
|
|
int i, j;
|
|
|
|
for (i = 0; i < TSENSE_SIZE; i++) {
|
|
if (!plat_data->sensor_data[i].zone_enable)
|
|
continue;
|
|
|
|
snprintf(name, THERMAL_NAME_LENGTH,
|
|
"%s-tsensor", sensor_names[i]);
|
|
/* Create a thermal zone device for each sensor */
|
|
thermal_zone_device_register(name,
|
|
0,
|
|
0,
|
|
(void *)i,
|
|
&soctherm_ops,
|
|
NULL,
|
|
0,
|
|
0);
|
|
}
|
|
|
|
for (i = 0; i < THERM_SIZE; i++) {
|
|
therm = &plat_data->therm[i];
|
|
if (!therm->zone_enable)
|
|
continue;
|
|
|
|
for (j = 0; j < therm->num_trips; j++)
|
|
soctherm_create_cooling_device(therm, i, j);
|
|
|
|
snprintf(name, THERMAL_NAME_LENGTH,
|
|
"%s-therm", therm_names[i]);
|
|
thz[i] = thermal_zone_device_register(
|
|
name,
|
|
therm->num_trips,
|
|
(1 << therm->num_trips) - 1,
|
|
(void *)TSENSE_SIZE + i,
|
|
&soctherm_ops,
|
|
therm->tzp,
|
|
therm->passive_delay,
|
|
0);
|
|
}
|
|
|
|
soctherm_update();
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
static inline void soctherm_update_zone(int zn)
|
|
{
|
|
}
|
|
static inline void soctherm_update(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static irqreturn_t soctherm_thermal_work_func(int irq, void *arg)
|
|
{
|
|
struct platform_device *pdev = arg;
|
|
u32 st, ex = 0, cp = 0, gp = 0;
|
|
|
|
st = soctherm_readl(INTR_STATUS);
|
|
|
|
/* deliberately clear expected interrupts handled in SW */
|
|
cp |= REG_GET_BIT(st, INTR_POS_CD0);
|
|
cp |= REG_GET_BIT(st, INTR_POS_CU0);
|
|
ex |= cp;
|
|
|
|
gp |= REG_GET_BIT(st, INTR_POS_GD0);
|
|
gp |= REG_GET_BIT(st, INTR_POS_GU0);
|
|
ex |= gp;
|
|
|
|
if (ex) {
|
|
soctherm_writel(ex, INTR_STATUS);
|
|
st &= ~ex;
|
|
if (cp)
|
|
soctherm_update_zone(THERM_CPU);
|
|
if (gp)
|
|
soctherm_update_zone(THERM_GPU);
|
|
}
|
|
|
|
/* deliberately ignore expected interrupts NOT handled in SW */
|
|
ex |= REG_GET_BIT(st, INTR_POS_PD0);
|
|
ex |= REG_GET_BIT(st, INTR_POS_PU0);
|
|
ex |= REG_GET_BIT(st, INTR_POS_MD0);
|
|
ex |= REG_GET_BIT(st, INTR_POS_MU0);
|
|
|
|
ex |= REG_GET_BIT(st, INTR_POS_CD1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_CU1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_CD2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_CU2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_CD3);
|
|
ex |= REG_GET_BIT(st, INTR_POS_CU3);
|
|
|
|
ex |= REG_GET_BIT(st, INTR_POS_GD1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_GU1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_GD2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_GU2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_GD3);
|
|
ex |= REG_GET_BIT(st, INTR_POS_GU3);
|
|
|
|
ex |= REG_GET_BIT(st, INTR_POS_PD1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_PU1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_PD2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_PU2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_PD3);
|
|
ex |= REG_GET_BIT(st, INTR_POS_PU3);
|
|
|
|
ex |= REG_GET_BIT(st, INTR_POS_MD1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_MU1);
|
|
ex |= REG_GET_BIT(st, INTR_POS_MD2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_MU2);
|
|
ex |= REG_GET_BIT(st, INTR_POS_MD3);
|
|
ex |= REG_GET_BIT(st, INTR_POS_MU3);
|
|
st &= ~ex;
|
|
|
|
if (st) {
|
|
/* Whine about any other unexpected INTR bits still set */
|
|
dev_err(&pdev->dev,
|
|
"soctherm: Ignored unexpected INTRs 0x%08x\n", st);
|
|
soctherm_writel(st, INTR_STATUS);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t soctherm_thermal_isr(int irq, void *arg)
|
|
{
|
|
u32 r;
|
|
|
|
r = soctherm_readl(INTR_STATUS);
|
|
soctherm_writel(r, INTR_DIS);
|
|
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
static void tegra_soctherm_throttle_program(enum soctherm_throttle_id throttle,
|
|
struct soctherm_throttle *data)
|
|
{
|
|
u32 r;
|
|
int i;
|
|
struct soctherm_throttle_dev *dev;
|
|
|
|
for (i = 0; i < THROTTLE_DEV_SIZE; i++) {
|
|
dev = &data->devs[i];
|
|
|
|
r = soctherm_readl(THROT_PSKIP_CTRL(throttle, i));
|
|
r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, dev->enable);
|
|
if (!dev->enable)
|
|
continue;
|
|
|
|
r = REG_SET(r, THROT_PSKIP_CTRL_DIVIDEND,
|
|
dev->dividend ?: throttle_defaults[throttle].dividend);
|
|
r = REG_SET(r, THROT_PSKIP_CTRL_DIVISOR,
|
|
dev->divisor ?: throttle_defaults[throttle].divisor);
|
|
soctherm_writel(r, THROT_PSKIP_CTRL(throttle, i));
|
|
|
|
r = soctherm_readl(THROT_PSKIP_RAMP(throttle, i));
|
|
r = REG_SET(r, THROT_PSKIP_RAMP_DURATION,
|
|
dev->duration ?: throttle_defaults[throttle].duration);
|
|
r = REG_SET(r, THROT_PSKIP_RAMP_STEP,
|
|
dev->step ?: throttle_defaults[throttle].step);
|
|
soctherm_writel(r, THROT_PSKIP_RAMP(throttle, i));
|
|
}
|
|
|
|
r = soctherm_readl(THROT_PRIORITY_LOCK);
|
|
if (r < data->priority) {
|
|
r = REG_SET(0, THROT_PRIORITY_LOCK_PRIORITY, data->priority);
|
|
soctherm_writel(r, THROT_PRIORITY_LOCK);
|
|
}
|
|
|
|
r = REG_SET(0, THROT_LITE_PRIORITY_PRIORITY, data->priority);
|
|
soctherm_writel(r, THROT_LITE_PRIORITY + THROT_OFFSET * throttle);
|
|
|
|
/* initialize stats collection */
|
|
r = STATS_CTL_CLR_DN | STATS_CTL_EN_DN |
|
|
STATS_CTL_CLR_UP | STATS_CTL_EN_UP;
|
|
soctherm_writel(r, STATS_CTL);
|
|
}
|
|
|
|
static void soctherm_tsense_program(enum soctherm_sense sensor,
|
|
struct soctherm_sensor *data)
|
|
{
|
|
u32 r;
|
|
|
|
r = REG_SET(0, TS_CPU0_CONFIG0_TALL, data->tall);
|
|
soctherm_writel(r, TS_TSENSE_REG_OFFSET(TS_CPU0_CONFIG0, sensor));
|
|
|
|
r = REG_SET(0, TS_CPU0_CONFIG1_TIDDQ, data->tiddq);
|
|
r = REG_SET(r, TS_CPU0_CONFIG1_EN, 1);
|
|
r = REG_SET(r, TS_CPU0_CONFIG1_TEN_COUNT, data->ten_count);
|
|
r = REG_SET(r, TS_CPU0_CONFIG1_TSAMPLE, data->tsample - 1);
|
|
soctherm_writel(r, TS_TSENSE_REG_OFFSET(TS_CPU0_CONFIG1, sensor));
|
|
}
|
|
|
|
static int soctherm_clk_init(struct platform_device *pdev)
|
|
{
|
|
int err = -EINVAL;
|
|
struct clk *clk_pllp = clk_get_sys(NULL, "pll_p");
|
|
struct clk *clk_clkm = clk_get_sys(NULL, "clk_m");
|
|
|
|
if (IS_ERR(clk_pllp) || IS_ERR(clk_clkm))
|
|
return -EINVAL;
|
|
|
|
plat_data->soctherm_clk = devm_clk_get(&pdev->dev, "soc_therm");
|
|
plat_data->tsensor_clk = devm_clk_get(&pdev->dev, "tsensor");
|
|
|
|
if (IS_ERR(plat_data->tsensor_clk) || IS_ERR(plat_data->soctherm_clk))
|
|
goto error;
|
|
|
|
err = clk_set_parent(plat_data->soctherm_clk, clk_pllp);
|
|
if (err) {
|
|
dev_err(&pdev->dev,
|
|
"soctherm: failed to set parent of soc_therm: %d\n",
|
|
err);
|
|
goto error;
|
|
}
|
|
err = clk_set_rate(plat_data->soctherm_clk,
|
|
plat_data->soctherm_clk_rate);
|
|
if (err) {
|
|
dev_err(&pdev->dev,
|
|
"soctherm: failed to set soc_therm rate to %lu: %d\n",
|
|
plat_data->soctherm_clk_rate, err);
|
|
goto error;
|
|
}
|
|
|
|
err = clk_set_parent(plat_data->tsensor_clk, clk_clkm);
|
|
if (err) {
|
|
dev_err(&pdev->dev,
|
|
"soctherm: failed to set parent of tegra-tsensor: %d\n",
|
|
err);
|
|
goto error;
|
|
}
|
|
err = clk_set_rate(plat_data->tsensor_clk,
|
|
plat_data->tsensor_clk_rate);
|
|
if (err) {
|
|
dev_err(&pdev->dev,
|
|
"soctherm: failed to set tegra-tsensor rate to %lu: %d\n",
|
|
plat_data->tsensor_clk_rate, err);
|
|
goto error;
|
|
}
|
|
|
|
return err;
|
|
|
|
error:
|
|
plat_data->soctherm_clk = NULL;
|
|
plat_data->tsensor_clk = NULL;
|
|
return err;
|
|
}
|
|
|
|
static int soctherm_clk_enable(bool enable)
|
|
{
|
|
int ret;
|
|
|
|
if (plat_data->soctherm_clk == NULL || plat_data->tsensor_clk == NULL)
|
|
return -EINVAL;
|
|
|
|
if (enable) {
|
|
ret = clk_prepare_enable(plat_data->soctherm_clk);
|
|
if (ret)
|
|
return ret;
|
|
ret = clk_prepare_enable(plat_data->tsensor_clk);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
clk_disable_unprepare(plat_data->soctherm_clk);
|
|
clk_disable_unprepare(plat_data->tsensor_clk);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void soctherm_fuse_read_calib_base(void)
|
|
{
|
|
struct soctherm_fuse_calib_data *calib_data;
|
|
|
|
calib_data = plat_data->fuse_calib_data;
|
|
|
|
fuse_calib_base_cp = calib_data->fuse_calib_base_cp;
|
|
fuse_calib_base_ft = calib_data->fuse_calib_base_ft;
|
|
|
|
/* use HI precision to calculate: use fuse_temp in 0.5C */
|
|
actual_temp_cp = calib_data->actual_temp_cp;
|
|
actual_temp_ft = calib_data->actual_temp_ft;
|
|
}
|
|
|
|
static int soctherm_fuse_read_tsensor(enum soctherm_sense sensor)
|
|
{
|
|
u32 r, value;
|
|
s32 calib, delta_sens, delta_temp;
|
|
s16 therm_a, therm_b;
|
|
s32 div, mult, actual_tsensor_ft, actual_tsensor_cp;
|
|
|
|
value = plat_data->fuse_calib_data->tsensor_calib[sensor].calib_val;
|
|
|
|
/* Extract bits and convert to signed 2's complement */
|
|
calib = REG_GET(value, FUSE_TSENSOR_CALIB_FT);
|
|
calib = MAKE_SIGNED32(calib, FUSE_TSENSOR_CALIB_BITS);
|
|
actual_tsensor_ft = (fuse_calib_base_ft * 32) + calib;
|
|
|
|
calib = REG_GET(value, FUSE_TSENSOR_CALIB_CP);
|
|
calib = MAKE_SIGNED32(calib, FUSE_TSENSOR_CALIB_BITS);
|
|
actual_tsensor_cp = (fuse_calib_base_cp * 64) + calib;
|
|
|
|
mult = plat_data->sensor_data[sensor].pdiv *
|
|
plat_data->sensor_data[sensor].tsamp_ate;
|
|
div = plat_data->sensor_data[sensor].tsample *
|
|
plat_data->sensor_data[sensor].pdiv_ate;
|
|
|
|
delta_sens = actual_tsensor_ft - actual_tsensor_cp;
|
|
delta_temp = actual_temp_ft - actual_temp_cp;
|
|
|
|
therm_a = div64_s64_precise((s64)delta_temp * (1LL << 13) * mult,
|
|
(s64)delta_sens * div);
|
|
|
|
therm_b = div64_s64_precise((((s64)actual_tsensor_ft * actual_temp_cp) -
|
|
((s64)actual_tsensor_cp * actual_temp_ft)),
|
|
(s64)delta_sens);
|
|
|
|
if (PRECISION_IS_LOWER()) {
|
|
int alpha, beta;
|
|
|
|
/* cp_fuse corrections */
|
|
alpha = plat_data->fuse_calib_data->fuse_corr_alpha[sensor];
|
|
beta = plat_data->fuse_calib_data->fuse_corr_beta[sensor];
|
|
|
|
therm_a = div64_s64_precise(
|
|
(s64)therm_a * alpha,
|
|
(s64)1000000LL);
|
|
therm_b = div64_s64_precise(
|
|
(s64)therm_b * alpha + beta,
|
|
(s64)1000000LL);
|
|
}
|
|
|
|
therm_a = LOWER_PRECISION_FOR_TEMP(therm_a);
|
|
therm_b = LOWER_PRECISION_FOR_TEMP(therm_b);
|
|
|
|
sensor2therm_a[sensor] = (s16)therm_a;
|
|
sensor2therm_b[sensor] = (s16)therm_b;
|
|
|
|
r = REG_SET(0, TS_CPU0_CONFIG2_THERM_A, therm_a);
|
|
r = REG_SET(r, TS_CPU0_CONFIG2_THERM_B, therm_b);
|
|
soctherm_writel(r, TS_TSENSE_REG_OFFSET(TS_CPU0_CONFIG2, sensor));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void soctherm_therm_trip_init(struct soctherm_tsensor_pmu_data *data)
|
|
{
|
|
u32 val, checksum;
|
|
|
|
if (!data)
|
|
return;
|
|
|
|
val = pmc_readl(PMC_SENSOR_CTRL);
|
|
val = REG_SET(val, PMC_SCRATCH_WRITE, 1);
|
|
val = REG_SET(val, PMC_ENABLE_RST, 1);
|
|
pmc_writel(val, PMC_SENSOR_CTRL);
|
|
|
|
/* Fill scratch registers to shutdown device on therm TRIP */
|
|
val = REG_SET(0, PMU_OFF_DATA, data->poweroff_reg_data);
|
|
val = REG_SET(val, PMU_OFF_ADDR, data->poweroff_reg_addr);
|
|
pmc_writel(val, PMC_SCRATCH54);
|
|
|
|
val = REG_SET(0, RESET_TEGRA, 1);
|
|
val = REG_SET(val, CONTROLLER_TYPE, data->controller_type);
|
|
val = REG_SET(val, I2C_CONTROLLER_ID, data->i2c_controller_id);
|
|
val = REG_SET(val, PINMUX, data->pinmux);
|
|
val = REG_SET(val, PMU_16BIT_SUPPORT, data->pmu_16bit_ops);
|
|
val = REG_SET(val, PMU_I2C_ADDRESS, data->pmu_i2c_addr);
|
|
|
|
checksum = data->poweroff_reg_addr +
|
|
data->poweroff_reg_data +
|
|
(val & 0xFF) +
|
|
((val >> 8) & 0xFF) +
|
|
((val >> 24) & 0xFF);
|
|
checksum &= 0xFF;
|
|
checksum = 0x100 - checksum;
|
|
|
|
val = REG_SET(val, CHECKSUM, checksum);
|
|
pmc_writel(val, PMC_SCRATCH55);
|
|
}
|
|
|
|
static int soctherm_init_platform_data(struct platform_device *pdev)
|
|
{
|
|
struct soctherm_therm *therm;
|
|
struct soctherm_sensor *s;
|
|
int i, j, k;
|
|
long rem;
|
|
u32 r;
|
|
|
|
/* initialize default values for unspecified params */
|
|
for (i = 0; i < TSENSE_SIZE; i++) {
|
|
therm = &plat_data->therm[tsensor2therm_map[i]];
|
|
s = &plat_data->sensor_data[i];
|
|
s->sensor_enable = s->zone_enable;
|
|
s->sensor_enable = s->sensor_enable ? s->sensor_enable : therm->zone_enable;
|
|
}
|
|
|
|
/* Pdiv */
|
|
r = soctherm_readl(TS_PDIV);
|
|
r = REG_SET(r, TS_PDIV_CPU, plat_data->sensor_data[TSENSE_CPU0].pdiv);
|
|
r = REG_SET(r, TS_PDIV_GPU, plat_data->sensor_data[TSENSE_GPU].pdiv);
|
|
r = REG_SET(r, TS_PDIV_MEM, plat_data->sensor_data[TSENSE_MEM0].pdiv);
|
|
r = REG_SET(r, TS_PDIV_PLLX, plat_data->sensor_data[TSENSE_PLLX].pdiv);
|
|
soctherm_writel(r, TS_PDIV);
|
|
|
|
/* Thermal Sensing programming */
|
|
soctherm_fuse_read_calib_base();
|
|
for (i = 0; i < TSENSE_SIZE; i++) {
|
|
if (plat_data->sensor_data[i].sensor_enable) {
|
|
soctherm_tsense_program(i, &plat_data->sensor_data[i]);
|
|
if (soctherm_fuse_read_tsensor(i) < 0)
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Sanitize therm trips */
|
|
for (i = 0; i < THERM_SIZE; i++) {
|
|
therm = &plat_data->therm[i];
|
|
if (!therm->zone_enable)
|
|
continue;
|
|
|
|
for (j = 0; j < therm->num_trips; j++) {
|
|
rem = therm->trips[j].trip_temp %
|
|
LOWER_PRECISION_FOR_CONV(1000);
|
|
if (rem) {
|
|
dev_warn(&pdev->dev,
|
|
"soctherm: zone%d/trip_point%d %ld mC rounded down\n",
|
|
i, j, therm->trips[j].trip_temp);
|
|
therm->trips[j].trip_temp -= rem;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Program hotspot offsets per THERM */
|
|
r = REG_SET(0, TS_HOTSPOT_OFF_CPU,
|
|
plat_data->therm[THERM_CPU].hotspot_offset / 1000);
|
|
r = REG_SET(r, TS_HOTSPOT_OFF_GPU,
|
|
plat_data->therm[THERM_GPU].hotspot_offset / 1000);
|
|
r = REG_SET(r, TS_HOTSPOT_OFF_MEM,
|
|
plat_data->therm[THERM_MEM].hotspot_offset / 1000);
|
|
soctherm_writel(r, TS_HOTSPOT_OFF);
|
|
|
|
/* Sanitize HW throttle priority */
|
|
for (i = 0; i < THROTTLE_SIZE; i++)
|
|
if (!plat_data->throttle[i].priority)
|
|
plat_data->throttle[i].priority = 0xE + i;
|
|
if (plat_data->throttle[THROTTLE_HEAVY].priority <
|
|
plat_data->throttle[THROTTLE_LIGHT].priority)
|
|
dev_err(&pdev->dev,
|
|
"ERROR: Priority of HEAVY less than LIGHT\n");
|
|
|
|
/* Thermal HW throttle programming */
|
|
for (i = 0; i < THROTTLE_SIZE; i++) {
|
|
/* Setup PSKIP parameters */
|
|
tegra_soctherm_throttle_program(i, &plat_data->throttle[i]);
|
|
|
|
/* Setup throttle thresholds per THERM */
|
|
for (j = 0; j < THERM_SIZE; j++) {
|
|
/* Check if PSKIP params are enabled */
|
|
if (!is_thermal_dev_throttle_enabled(plat_data, i, j))
|
|
continue;
|
|
|
|
therm = &plat_data->therm[j];
|
|
for (k = 0; k < therm->num_trips; k++)
|
|
if ((therm->trips[k].trip_type ==
|
|
THERMAL_TRIP_HOT) &&
|
|
strnstr(therm->trips[k].cdev_type,
|
|
i == THROTTLE_HEAVY ? "heavy" :
|
|
"light", THERMAL_NAME_LENGTH))
|
|
break;
|
|
if (k < therm->num_trips)
|
|
prog_hw_threshold(&therm->trips[k], j, i);
|
|
}
|
|
}
|
|
|
|
/* Enable PMC to shutdown */
|
|
soctherm_therm_trip_init(plat_data->tshut_pmu_trip_data);
|
|
|
|
r = clk_reset_readl(CAR_SUPER_CCLKG_DIVIDER);
|
|
r = REG_SET(r, CDIVG_USE_THERM_CONTROLS, 1);
|
|
clk_reset_writel(r, CAR_SUPER_CCLKG_DIVIDER);
|
|
|
|
/* Thermtrip */
|
|
for (i = 0; i < THERM_SIZE; i++) {
|
|
therm = &plat_data->therm[i];
|
|
if (!therm->zone_enable)
|
|
continue;
|
|
|
|
for (j = 0; j < therm->num_trips; j++)
|
|
if (therm->trips[j].trip_type == THERMAL_TRIP_CRITICAL)
|
|
prog_hw_shutdown(&therm->trips[j], i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_suspend(struct device *dev)
|
|
{
|
|
soctherm_writel((u32)-1, INTR_DIS);
|
|
disable_irq(plat_data->thermal_irq);
|
|
soctherm_clk_enable(false);
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
soctherm_clk_enable(true);
|
|
soctherm_init_platform_data(pdev);
|
|
soctherm_update();
|
|
enable_irq(plat_data->thermal_irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int soctherm_platform_init(struct platform_device *pdev)
|
|
{
|
|
struct soctherm_platform_data *data = platform_get_drvdata(pdev);
|
|
int err;
|
|
|
|
if (!data)
|
|
return -EINVAL;
|
|
plat_data = data;
|
|
|
|
plat_data->read_hw_temp = true;
|
|
|
|
if (soctherm_clk_init(pdev) < 0)
|
|
return -EINVAL;
|
|
|
|
if (soctherm_clk_enable(true) < 0)
|
|
return -EINVAL;
|
|
|
|
if (soctherm_init_platform_data(pdev) < 0)
|
|
return -EINVAL;
|
|
|
|
plat_data->thermal_irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
|
|
if (plat_data->thermal_irq <= 0) {
|
|
dev_err(&pdev->dev, "Failed to map thermal IRQ\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* enable threaded interrupts */
|
|
err = devm_request_threaded_irq(&pdev->dev, plat_data->thermal_irq,
|
|
soctherm_thermal_isr,
|
|
soctherm_thermal_work_func,
|
|
IRQF_ONESHOT,
|
|
"soctherm_thermal", pdev);
|
|
if (err < 0)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int regs_show(struct seq_file *s, void *data)
|
|
{
|
|
u32 r;
|
|
u32 state;
|
|
int tcpu[TSENSE_SIZE];
|
|
int i, level;
|
|
|
|
seq_printf(s, "-----TSENSE (precision %s convert %s)-----\n",
|
|
PRECISION_TO_STR(), plat_data->read_hw_temp ? "HW" : "SW");
|
|
for (i = 0; i < TSENSE_SIZE; i++) {
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(TS_CPU0_CONFIG1, i));
|
|
state = REG_GET(r, TS_CPU0_CONFIG1_EN);
|
|
if (!state)
|
|
continue;
|
|
|
|
seq_printf(s, "%s: ", sensor_names[i]);
|
|
|
|
seq_printf(s, "En(%d) ", state);
|
|
state = REG_GET(r, TS_CPU0_CONFIG1_TIDDQ);
|
|
seq_printf(s, "tiddq(%d) ", state);
|
|
state = REG_GET(r, TS_CPU0_CONFIG1_TEN_COUNT);
|
|
seq_printf(s, "ten_count(%d) ", state);
|
|
state = REG_GET(r, TS_CPU0_CONFIG1_TSAMPLE);
|
|
seq_printf(s, "tsample(%d) ", state + 1);
|
|
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(TS_CPU0_STATUS1, i));
|
|
state = REG_GET(r, TS_CPU0_STATUS1_TEMP_VALID);
|
|
seq_printf(s, "Temp(%d/", state);
|
|
state = REG_GET(r, TS_CPU0_STATUS1_TEMP);
|
|
seq_printf(s, "%d) ", tcpu[i] = temp_translate(state));
|
|
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(TS_CPU0_STATUS0, i));
|
|
state = REG_GET(r, TS_CPU0_STATUS0_VALID);
|
|
seq_printf(s, "Capture(%d/", state);
|
|
state = REG_GET(r, TS_CPU0_STATUS0_CAPTURE);
|
|
seq_printf(s, "%d) (Converted-temp(%ld) ", state,
|
|
temp_convert(state, sensor2therm_a[i],
|
|
sensor2therm_b[i]));
|
|
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(TS_CPU0_CONFIG0, i));
|
|
state = REG_GET(r, TS_CPU0_CONFIG0_TALL);
|
|
seq_printf(s, "Tall(%d) ", state);
|
|
state = REG_GET(r, TS_CPU0_CONFIG0_TCALC_OVER);
|
|
seq_printf(s, "Over(%d/", state);
|
|
state = REG_GET(r, TS_CPU0_CONFIG0_OVER);
|
|
seq_printf(s, "%d/", state);
|
|
state = REG_GET(r, TS_CPU0_CONFIG0_CPTR_OVER);
|
|
seq_printf(s, "%d) ", state);
|
|
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(TS_CPU0_CONFIG2, i));
|
|
state = REG_GET(r, TS_CPU0_CONFIG2_THERM_A);
|
|
seq_printf(s, "Therm_A/B(%d/", state);
|
|
state = REG_GET(r, TS_CPU0_CONFIG2_THERM_B);
|
|
seq_printf(s, "%d)\n", (s16)state);
|
|
}
|
|
|
|
r = soctherm_readl(TS_PDIV);
|
|
seq_printf(s, "PDIV: 0x%x\n", r);
|
|
|
|
seq_puts(s, "\n");
|
|
seq_puts(s, "-----SOC_THERM-----\n");
|
|
|
|
r = soctherm_readl(TS_TEMP1);
|
|
state = REG_GET(r, TS_TEMP1_CPU_TEMP);
|
|
seq_printf(s, "Temperatures: CPU(%ld) ", temp_translate(state));
|
|
state = REG_GET(r, TS_TEMP1_GPU_TEMP);
|
|
seq_printf(s, " GPU(%ld) ", temp_translate(state));
|
|
r = soctherm_readl(TS_TEMP2);
|
|
state = REG_GET(r, TS_TEMP2_PLLX_TEMP);
|
|
seq_printf(s, " PLLX(%ld) ", temp_translate(state));
|
|
state = REG_GET(r, TS_TEMP2_MEM_TEMP);
|
|
seq_printf(s, " MEM(%ld)\n", temp_translate(state));
|
|
|
|
for (i = 0; i < THERM_SIZE; i++) {
|
|
if (i == THERM_MEM)
|
|
continue;
|
|
seq_printf(s, "%s:\n", therm_names[i]);
|
|
|
|
for (level = 0; level < 4; level++) {
|
|
r = soctherm_readl(TS_THERM_REG_OFFSET(CTL_LVL0_CPU0,
|
|
level, i));
|
|
state = REG_GET(r, CTL_LVL0_CPU0_UP_THRESH);
|
|
seq_printf(s, " %d: Up/Dn(%d/", level,
|
|
LOWER_PRECISION_FOR_CONV(state));
|
|
state = REG_GET(r, CTL_LVL0_CPU0_DN_THRESH);
|
|
seq_printf(s, "%d) ", LOWER_PRECISION_FOR_CONV(state));
|
|
state = REG_GET(r, CTL_LVL0_CPU0_EN);
|
|
seq_printf(s, "En(%d) ", state);
|
|
state = REG_GET(r, CTL_LVL0_CPU0_CPU_THROT);
|
|
seq_puts(s, "Throt-CPU");
|
|
seq_printf(s, "(%s) ", state ?
|
|
state == CTL_LVL0_CPU0_DEV_THROT_LIGHT ? "L" :
|
|
state == CTL_LVL0_CPU0_DEV_THROT_HEAVY ? "H" :
|
|
"H+L" : "none");
|
|
state = REG_GET(r, CTL_LVL0_CPU0_GPU_THROT);
|
|
seq_puts(s, "Throt-GPU");
|
|
seq_printf(s, "(%s) ", state ?
|
|
state == CTL_LVL0_CPU0_DEV_THROT_LIGHT ? "L" :
|
|
state == CTL_LVL0_CPU0_DEV_THROT_HEAVY ? "H" :
|
|
"H+L" : "none");
|
|
state = REG_GET(r, CTL_LVL0_CPU0_STATUS);
|
|
seq_printf(s, "Status(%s)\n",
|
|
state == 0 ? "LO" :
|
|
state == 1 ? "in" :
|
|
state == 2 ? "??" : "HI");
|
|
}
|
|
}
|
|
|
|
r = soctherm_readl(STATS_CTL);
|
|
seq_printf(s, "STATS: Up(%s) Dn(%s)\n",
|
|
r & STATS_CTL_EN_UP ? "En" : "--",
|
|
r & STATS_CTL_EN_DN ? "En" : "--");
|
|
for (level = 0; level < 4; level++) {
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(UP_STATS_L0, level));
|
|
seq_printf(s, " Level_%d Up(%d) ", level, r);
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(DN_STATS_L0, level));
|
|
seq_printf(s, "Dn(%d)\n", r);
|
|
}
|
|
|
|
r = soctherm_readl(THERMTRIP);
|
|
state = REG_GET(r, THERMTRIP_ANY_EN);
|
|
seq_printf(s, "ThermTRIP ANY En(%d)\n", state);
|
|
state = REG_GET(r, THERMTRIP_CPU_EN);
|
|
seq_printf(s, " CPU En(%d) ", state);
|
|
state = REG_GET(r, THERMTRIP_CPU_THRESH);
|
|
seq_printf(s, "Thresh(%d)\n", LOWER_PRECISION_FOR_CONV(state));
|
|
state = REG_GET(r, THERMTRIP_GPU_EN);
|
|
seq_printf(s, " GPU En(%d) ", state);
|
|
state = REG_GET(r, THERMTRIP_GPUMEM_THRESH);
|
|
seq_printf(s, "Thresh(%d)\n", LOWER_PRECISION_FOR_CONV(state));
|
|
state = REG_GET(r, THERMTRIP_TSENSE_EN);
|
|
seq_printf(s, " PLLX En(%d) ", state);
|
|
state = REG_GET(r, THERMTRIP_TSENSE_THRESH);
|
|
seq_printf(s, "Thresh(%d)\n", LOWER_PRECISION_FOR_CONV(state));
|
|
|
|
r = soctherm_readl(THROT_GLOBAL_CFG);
|
|
seq_printf(s, "GLOBAL THROTTLE CONFIG: 0x%x\n", r);
|
|
|
|
r = soctherm_readl(THROT_STATUS);
|
|
state = REG_GET(r, THROT_STATUS_BREACH);
|
|
seq_printf(s, "THROT STATUS: breach(%d) ", state);
|
|
state = REG_GET(r, THROT_STATUS_STATE);
|
|
seq_printf(s, "state(%d) ", state);
|
|
state = REG_GET(r, THROT_STATUS_ENABLED);
|
|
seq_printf(s, "enabled(%d)\n", state);
|
|
|
|
r = soctherm_readl(CPU_PSKIP_STATUS);
|
|
state = REG_GET(r, CPU_PSKIP_STATUS_M);
|
|
seq_printf(s, "CPU PSKIP: M(%d) ", state);
|
|
state = REG_GET(r, CPU_PSKIP_STATUS_N);
|
|
seq_printf(s, "N(%d) ", state);
|
|
state = REG_GET(r, CPU_PSKIP_STATUS_ENABLED);
|
|
seq_printf(s, "enabled(%d)\n", state);
|
|
|
|
r = soctherm_readl(THROT_PSKIP_CTRL(THROTTLE_LIGHT, THROTTLE_DEV_CPU));
|
|
state = REG_GET(r, THROT_PSKIP_CTRL_ENABLE);
|
|
seq_printf(s, "CPU PSKIP LIGHT: enabled(%d) ", state);
|
|
state = REG_GET(r, THROT_PSKIP_CTRL_DIVIDEND);
|
|
seq_printf(s, "dividend(%d) ", state);
|
|
state = REG_GET(r, THROT_PSKIP_CTRL_DIVISOR);
|
|
seq_printf(s, "divisor(%d) ", state);
|
|
r = soctherm_readl(THROT_PSKIP_RAMP(THROTTLE_LIGHT, THROTTLE_DEV_CPU));
|
|
state = REG_GET(r, THROT_PSKIP_RAMP_DURATION);
|
|
seq_printf(s, "duration(%d) ", state);
|
|
state = REG_GET(r, THROT_PSKIP_RAMP_STEP);
|
|
seq_printf(s, "step(%d)\n", state);
|
|
|
|
r = soctherm_readl(THROT_PSKIP_CTRL(THROTTLE_HEAVY, THROTTLE_DEV_CPU));
|
|
state = REG_GET(r, THROT_PSKIP_CTRL_ENABLE);
|
|
seq_printf(s, "CPU PSKIP HEAVY: enabled(%d) ", state);
|
|
state = REG_GET(r, THROT_PSKIP_CTRL_DIVIDEND);
|
|
seq_printf(s, "dividend(%d) ", state);
|
|
state = REG_GET(r, THROT_PSKIP_CTRL_DIVISOR);
|
|
seq_printf(s, "divisor(%d) ", state);
|
|
r = soctherm_readl(THROT_PSKIP_RAMP(THROTTLE_HEAVY, THROTTLE_DEV_CPU));
|
|
state = REG_GET(r, THROT_PSKIP_RAMP_DURATION);
|
|
seq_printf(s, "duration(%d) ", state);
|
|
state = REG_GET(r, THROT_PSKIP_RAMP_STEP);
|
|
seq_printf(s, "step(%d)\n", state);
|
|
return 0;
|
|
}
|
|
|
|
static int temp_log_show(struct seq_file *s, void *data)
|
|
{
|
|
u32 r, state;
|
|
int i;
|
|
u64 ts;
|
|
u_long ns;
|
|
|
|
ts = cpu_clock(0);
|
|
ns = do_div(ts, 1000000000);
|
|
seq_printf(s, "%6lu.%06lu", (u_long) ts, ns / 1000);
|
|
|
|
for (i = 0; i < TSENSE_SIZE; i++) {
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(
|
|
TS_CPU0_CONFIG1, i));
|
|
state = REG_GET(r, TS_CPU0_CONFIG1_EN);
|
|
if (!state)
|
|
continue;
|
|
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(
|
|
TS_CPU0_STATUS1, i));
|
|
if (!REG_GET(r, TS_CPU0_STATUS1_TEMP_VALID)) {
|
|
seq_puts(s, "\tINVALID");
|
|
continue;
|
|
}
|
|
|
|
if (plat_data->read_hw_temp) {
|
|
state = REG_GET(r, TS_CPU0_STATUS1_TEMP);
|
|
seq_printf(s, "\t%ld", temp_translate(state));
|
|
} else {
|
|
r = soctherm_readl(TS_TSENSE_REG_OFFSET(
|
|
TS_CPU0_STATUS0, i));
|
|
state = REG_GET(r, TS_CPU0_STATUS0_CAPTURE);
|
|
seq_printf(s, "\t%ld",
|
|
temp_convert(state,
|
|
sensor2therm_a[i],
|
|
sensor2therm_b[i]));
|
|
}
|
|
}
|
|
seq_puts(s, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int regs_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, regs_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations regs_fops = {
|
|
.open = regs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int convert_get(void *data, u64 *val)
|
|
{
|
|
*val = !plat_data->read_hw_temp;
|
|
return 0;
|
|
}
|
|
static int convert_set(void *data, u64 val)
|
|
{
|
|
plat_data->read_hw_temp = !val;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(convert_fops, convert_get, convert_set, "%llu\n");
|
|
|
|
static int temp_log_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, temp_log_show, inode->i_private);
|
|
}
|
|
static const struct file_operations temp_log_fops = {
|
|
.open = temp_log_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int soctherm_debug_init(struct platform_device *pdev)
|
|
{
|
|
struct dentry *tegra_soctherm_root;
|
|
|
|
tegra_soctherm_root = debugfs_create_dir("tegra_soctherm", NULL);
|
|
debugfs_create_file("regs", 0644, tegra_soctherm_root,
|
|
NULL, ®s_fops);
|
|
debugfs_create_file("convert", 0644, tegra_soctherm_root,
|
|
NULL, &convert_fops);
|
|
debugfs_create_file("temp_log", 0644, tegra_soctherm_root,
|
|
pdev, &temp_log_fops);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void soctherm_add_trip_points(struct platform_device *pdev,
|
|
struct thermal_trip_info *trips,
|
|
int *num_trips,
|
|
struct tegra_cooling_device *cdev_data)
|
|
{
|
|
int i, num;
|
|
struct thermal_trip_info *trip_state;
|
|
|
|
if (!trips || !num_trips || !cdev_data)
|
|
return;
|
|
|
|
num = cdev_data->trip_temperatures_num;
|
|
if (*num_trips + num > THERMAL_MAX_TRIPS) {
|
|
dev_warn(&pdev->dev, "cdev %s has too many trips\n",
|
|
cdev_data->cdev_type);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < (*num_trips); i++) {
|
|
trip_state = &trips[i];
|
|
if (!strcmp(trip_state->cdev_type, cdev_data->cdev_type)) {
|
|
dev_warn(&pdev->dev, "cdev %s trips already added\n",
|
|
cdev_data->cdev_type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < num; i++) {
|
|
trip_state = &trips[*num_trips];
|
|
|
|
trip_state->cdev_type = cdev_data->cdev_type;
|
|
trip_state->trip_temp = cdev_data->trip_temperatures[i] * 1000;
|
|
trip_state->trip_type = THERMAL_TRIP_ACTIVE;
|
|
trip_state->upper = trip_state->lower = i + 1;
|
|
trip_state->hysteresis = 1000;
|
|
|
|
(*num_trips)++;
|
|
}
|
|
}
|
|
|
|
int soctherm_parse_pmu_dt(struct platform_device *pdev)
|
|
{
|
|
struct soctherm_platform_data *pdata = platform_get_drvdata(pdev);
|
|
struct device_node *soctherm_dn = pdev->dev.of_node;
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = of_property_read_u32(soctherm_dn, "pmu-16bit-ops", &val);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to read pmu-16bit-ops\n");
|
|
return ret;
|
|
}
|
|
pdata->tshut_pmu_trip_data->pmu_16bit_ops = val;
|
|
|
|
/* use I2C mode */
|
|
pdata->tshut_pmu_trip_data->controller_type = 0;
|
|
|
|
ret = of_property_read_u32(soctherm_dn, "pmu-i2c-addr", &val);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to read pmu-i2c-addr\n");
|
|
return ret;
|
|
}
|
|
pdata->tshut_pmu_trip_data->pmu_i2c_addr = val;
|
|
|
|
ret = of_property_read_u32(soctherm_dn, "i2c-controller-id", &val);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to read i2c-controller-id\n");
|
|
return ret;
|
|
}
|
|
pdata->tshut_pmu_trip_data->i2c_controller_id = val;
|
|
|
|
ret = of_property_read_u32(soctherm_dn, "poweroff-reg-addr", &val);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to read poweroff-reg-addr\n");
|
|
return ret;
|
|
}
|
|
pdata->tshut_pmu_trip_data->poweroff_reg_addr = val;
|
|
|
|
ret = of_property_read_u32(soctherm_dn, "poweroff-reg-data", &val);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to read poweroff-reg-data\n");
|
|
return ret;
|
|
}
|
|
pdata->tshut_pmu_trip_data->poweroff_reg_data = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void soctherm_init_sensor(struct platform_device *pdev,
|
|
const struct soctherm_sensor *sensor)
|
|
{
|
|
int i;
|
|
struct soctherm_platform_data *pdata = platform_get_drvdata(pdev);
|
|
|
|
for (i = 0; i < TSENSE_SIZE; i++) {
|
|
struct soctherm_sensor *s;
|
|
s = &pdata->sensor_data[i];
|
|
s->tall = s->tall ?: sensor->tall;
|
|
s->tiddq = s->tiddq ?: sensor->tiddq;
|
|
s->ten_count = s->ten_count ?: sensor->ten_count;
|
|
s->tsample = s->tsample ?: sensor->tsample;
|
|
s->tsamp_ate = s->tsamp_ate ?: sensor->tsamp_ate;
|
|
s->pdiv = s->pdiv ?: sensor->pdiv;
|
|
s->pdiv_ate = s->pdiv_ate ?: sensor->pdiv_ate;
|
|
}
|
|
}
|
|
|
|
void soctherm_init_clk_rate(struct platform_device *pdev,
|
|
unsigned long soctherm_clk_rate,
|
|
unsigned long tsensor_clk_rate)
|
|
{
|
|
struct soctherm_platform_data *pdata = platform_get_drvdata(pdev);
|
|
|
|
if (!pdata->soctherm_clk_rate)
|
|
pdata->soctherm_clk_rate = soctherm_clk_rate;
|
|
|
|
if (!pdata->tsensor_clk_rate)
|
|
pdata->tsensor_clk_rate = tsensor_clk_rate;
|
|
}
|
|
|
|
static const struct of_device_id tegra_soctherm_match[] = {
|
|
{ .compatible = "nvidia,tegra114-soctherm",
|
|
.data = &tegra114_soctherm_init, },
|
|
{ .compatible = "nvidia,tegra124-soctherm",
|
|
.data = &tegra124_soctherm_init, },
|
|
{},
|
|
};
|
|
|
|
typedef int (*of_soctherm_init)(struct device_node *);
|
|
|
|
static int tegra_soctherm_probe(struct platform_device *pdev)
|
|
{
|
|
const struct of_device_id *matches;
|
|
struct device_node *np;
|
|
int ret;
|
|
|
|
matches = tegra_soctherm_match;
|
|
for_each_matching_node(np, matches) {
|
|
const struct of_device_id *match = of_match_node(matches, np);
|
|
of_soctherm_init soctherm_init = (of_soctherm_init)match->data;
|
|
|
|
if (!soctherm_init)
|
|
return -EINVAL;
|
|
ret = soctherm_init(np);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ret = soctherm_platform_init(pdev);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
soctherm_thermal_sys_init(pdev);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
soctherm_debug_init(pdev);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_soctherm_remove(struct platform_device *pdev)
|
|
{
|
|
plat_data->soctherm_clk = NULL;
|
|
plat_data->tsensor_clk = NULL;
|
|
|
|
dev_info(&pdev->dev, "%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static SIMPLE_DEV_PM_OPS(tegra_soctherm_pm,
|
|
soctherm_suspend, soctherm_resume);
|
|
#define TEGRA_SOC_THERM_PM (&tegra_soctherm_pm)
|
|
#else
|
|
#define TEGRA_SOC_THERM_PM NULL
|
|
#endif
|
|
|
|
static struct platform_driver tegra_soctherm_driver = {
|
|
.driver = {
|
|
.name = "tegra_soctherm",
|
|
.owner = THIS_MODULE,
|
|
.pm = TEGRA_SOC_THERM_PM,
|
|
.of_match_table = tegra_soctherm_match,
|
|
},
|
|
.probe = tegra_soctherm_probe,
|
|
.remove = tegra_soctherm_remove,
|
|
};
|
|
|
|
static int __init tegra_soctherm_init(void)
|
|
{
|
|
return platform_driver_register(&tegra_soctherm_driver);
|
|
}
|
|
|
|
static void __exit tegra_soctherm_exit(void)
|
|
{
|
|
platform_driver_unregister(&tegra_soctherm_driver);
|
|
}
|
|
|
|
module_init(tegra_soctherm_init);
|
|
module_exit(tegra_soctherm_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Tegra1x soc thermal");
|