/* * arch/arm/mach-tegra/tegra11_soctherm.c * * Copyright (c) 2011-2014, 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iomap.h" #include "tegra11_soctherm.h" #include "gpio-names.h" #include "common.h" #include "dvfs.h" static const int MAX_HIGH_TEMP = 127000; static const int MIN_LOW_TEMP = -127000; /* Min temp granularity specified as X in 2^X. * -1: Hi precision option: 2^-1 = 0.5C (T12x onwards) * 0: Lo precision option: 2^0 = 1.0C */ #ifdef CONFIG_ARCH_TEGRA_12x_SOC static const int precision = -1; /* Use high precision on T12x */ #else static const int precision; /* default 0 -> low precision */ #endif #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_CPU_THROT_SHIFT 5 #define CTL_LVL0_CPU0_CPU_THROT_MASK 0x3 #define CTL_LVL0_CPU0_CPU_THROT_LIGHT 0x1 #define CTL_LVL0_CPU0_CPU_THROT_HEAVY 0x2 #define CTL_LVL0_CPU0_GPU_THROT_SHIFT 3 #define CTL_LVL0_CPU0_GPU_THROT_MASK 0x3 #define CTL_LVL0_CPU0_GPU_THROT_LIGHT 0x1 #define CTL_LVL0_CPU0_GPU_THROT_HEAVY 0x2 #define CTL_LVL0_CPU0_MEM_THROT_SHIFT 2 #define CTL_LVL0_CPU0_MEM_THROT_MASK 0x1 #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 TS_TEMP_SW_OVERRIDE 0x1d8 #define TH_INTR_STATUS 0x84 #define TH_INTR_ENABLE 0x88 #define TH_INTR_DISABLE 0x8c #define LOCK_CTL 0x90 #define TH_INTR_POS_MD3_SHIFT 31 #define TH_INTR_POS_MD3_MASK 0x1 #define TH_INTR_POS_MU3_SHIFT 30 #define TH_INTR_POS_MU3_MASK 0x1 #define TH_INTR_POS_MD2_SHIFT 29 #define TH_INTR_POS_MD2_MASK 0x1 #define TH_INTR_POS_MU2_SHIFT 28 #define TH_INTR_POS_MU2_MASK 0x1 #define TH_INTR_POS_MD1_SHIFT 27 #define TH_INTR_POS_MD1_MASK 0x1 #define TH_INTR_POS_MU1_SHIFT 26 #define TH_INTR_POS_MU1_MASK 0x1 #define TH_INTR_POS_MD0_SHIFT 25 #define TH_INTR_POS_MD0_MASK 0x1 #define TH_INTR_POS_MU0_SHIFT 24 #define TH_INTR_POS_MU0_MASK 0x1 #define TH_INTR_POS_GD3_SHIFT 23 #define TH_INTR_POS_GD3_MASK 0x1 #define TH_INTR_POS_GU3_SHIFT 22 #define TH_INTR_POS_GU3_MASK 0x1 #define TH_INTR_POS_GD2_SHIFT 21 #define TH_INTR_POS_GD2_MASK 0x1 #define TH_INTR_POS_GU2_SHIFT 20 #define TH_INTR_POS_GU2_MASK 0x1 #define TH_INTR_POS_GD1_SHIFT 19 #define TH_INTR_POS_GD1_MASK 0x1 #define TH_INTR_POS_GU1_SHIFT 18 #define TH_INTR_POS_GU1_MASK 0x1 #define TH_INTR_POS_GD0_SHIFT 17 #define TH_INTR_POS_GD0_MASK 0x1 #define TH_INTR_POS_GU0_SHIFT 16 #define TH_INTR_POS_GU0_MASK 0x1 #define TH_INTR_POS_CD3_SHIFT 15 #define TH_INTR_POS_CD3_MASK 0x1 #define TH_INTR_POS_CU3_SHIFT 14 #define TH_INTR_POS_CU3_MASK 0x1 #define TH_INTR_POS_CD2_SHIFT 13 #define TH_INTR_POS_CD2_MASK 0x1 #define TH_INTR_POS_CU2_SHIFT 12 #define TH_INTR_POS_CU2_MASK 0x1 #define TH_INTR_POS_CD1_SHIFT 11 #define TH_INTR_POS_CD1_MASK 0x1 #define TH_INTR_POS_CU1_SHIFT 10 #define TH_INTR_POS_CU1_MASK 0x1 #define TH_INTR_POS_CD0_SHIFT 9 #define TH_INTR_POS_CD0_MASK 0x1 #define TH_INTR_POS_CU0_SHIFT 8 #define TH_INTR_POS_CU0_MASK 0x1 #define TH_INTR_POS_PD3_SHIFT 7 #define TH_INTR_POS_PD3_MASK 0x1 #define TH_INTR_POS_PU3_SHIFT 6 #define TH_INTR_POS_PU3_MASK 0x1 #define TH_INTR_POS_PD2_SHIFT 5 #define TH_INTR_POS_PD2_MASK 0x1 #define TH_INTR_POS_PU2_SHIFT 4 #define TH_INTR_POS_PU2_MASK 0x1 #define TH_INTR_POS_PD1_SHIFT 3 #define TH_INTR_POS_PD1_MASK 0x1 #define TH_INTR_POS_PU1_SHIFT 2 #define TH_INTR_POS_PU1_MASK 0x1 #define TH_INTR_POS_PD0_SHIFT 1 #define TH_INTR_POS_PD0_MASK 0x1 #define TH_INTR_POS_PU0_SHIFT 0 #define TH_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 THROT13_GLOBAL_CFG 0x148 #define THROT_GLOBAL_ENB_SHIFT 0 #define THROT_GLOBAL_ENB_MASK 0x1 #define OC1_CFG 0x310 #define OC1_CFG_LONG_LATENCY_SHIFT 6 #define OC1_CFG_LONG_LATENCY_MASK 0x1 #define OC1_CFG_HW_RESTORE_SHIFT 5 #define OC1_CFG_HW_RESTORE_MASK 0x1 #define OC1_CFG_PWR_GOOD_MASK_SHIFT 4 #define OC1_CFG_PWR_GOOD_MASK_MASK 0x1 #define OC1_CFG_THROTTLE_MODE_SHIFT 2 #define OC1_CFG_THROTTLE_MODE_MASK 0x3 #define OC1_CFG_ALARM_POLARITY_SHIFT 1 #define OC1_CFG_ALARM_POLARITY_MASK 0x1 #define OC1_CFG_EN_THROTTLE_SHIFT 0 #define OC1_CFG_EN_THROTTLE_MASK 0x1 #define OC1_CNT_THRESHOLD 0x314 #define OC1_THROTTLE_PERIOD 0x318 #define OC1_ALARM_COUNT 0x31c #define OC1_FILTER 0x320 #define OC1_STATS 0x3a8 #define OC_INTR_STATUS 0x39c #define OC_INTR_ENABLE 0x3a0 #define OC_INTR_DISABLE 0x3a4 #define OC_INTR_POS_OC1_SHIFT 0 #define OC_INTR_POS_OC1_MASK 0x1 #define OC_INTR_POS_OC2_SHIFT 1 #define OC_INTR_POS_OC2_MASK 0x1 #define OC_INTR_POS_OC3_SHIFT 2 #define OC_INTR_POS_OC3_MASK 0x1 #define OC_INTR_POS_OC4_SHIFT 3 #define OC_INTR_POS_OC4_MASK 0x1 #define OC_INTR_POS_OC5_SHIFT 4 #define OC_INTR_POS_OC5_MASK 0x1 #define OC_STATS_CTL 0x3c4 #define OC_STATS_CTL_CLR_ALL 0x2 #define OC_STATS_CTL_EN_ALL 0x1 #define CPU_PSKIP_STATUS 0x418 #define GPU_PSKIP_STATUS 0x41c #define XPU_PSKIP_STATUS_M_SHIFT 12 #define XPU_PSKIP_STATUS_M_MASK 0xff #define XPU_PSKIP_STATUS_N_SHIFT 4 #define XPU_PSKIP_STATUS_N_MASK 0xff #define XPU_PSKIP_STATUS_SW_OVERRIDE_SHIFT 1 #define XPU_PSKIP_STATUS_SW_OVERRIDE_MASK 0x1 #define XPU_PSKIP_STATUS_ENABLED_SHIFT 0 #define XPU_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 THROT13_PSKIP_CTRL_LOW_CPU 0x154 #define THROT_PSKIP_CTRL_ENABLE_SHIFT 31 #define THROT_PSKIP_CTRL_ENABLE_MASK 0x1 #define THROT_PSKIP_CTRL_VECT_GPU_SHIFT 16 #define THROT_PSKIP_CTRL_VECT_GPU_MASK 0x7 #define THROT_PSKIP_CTRL_VECT_CPU_SHIFT 8 #define THROT_PSKIP_CTRL_VECT_CPU_MASK 0x7 #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_CTRL_VECT2_CPU_SHIFT 0 #define THROT_PSKIP_CTRL_VECT2_CPU_MASK 0x7 #define THROT_PSKIP_RAMP_LITE_CPU 0x434 #define THROT13_PSKIP_RAMP_LOW_CPU 0x150 #define THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_SHIFT 31 #define THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK 0x1 #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_VECT_NONE 0x0 /* 3'b000 */ #define THROT_VECT_LOW 0x1 /* 3'b001 */ #define THROT_VECT_MED 0x3 /* 3'b011 */ #define THROT_VECT_HVY 0x7 /* 3'b111 */ #define THROT_LEVEL_LOW 0 #define THROT_LEVEL_MED 1 #define THROT_LEVEL_HVY 2 #define THROT_LEVEL_NONE -1 /* invalid */ #define THROT_PRIORITY_LITE 0x444 #define THROT_PRIORITY_LITE_PRIO_SHIFT 0 #define THROT_PRIORITY_LITE_PRIO_MASK 0xff #define THROT_DELAY_LITE 0x448 #define THROT_DELAY_LITE_DELAY_SHIFT 0 #define THROT_DELAY_LITE_DELAY_MASK 0xff #define THROT_OFFSET 0x30 #define THROT13_OFFSET 0x0c #define ALARM_OFFSET 0x14 #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 CAR13_SUPER_CCLKG_DIVIDER 0x024 #define CDIVG_ENABLE_SHIFT 31 #define CDIVG_ENABLE_MASK 0x1 #define CDIVG_USE_THERM_CONTROLS_SHIFT 30 #define CDIVG_USE_THERM_CONTROLS_MASK 0x1 #define CDIVG_DIVIDEND_MASK 0xff #define CDIVG_DIVIDEND_SHIFT 8 #define CDIVG_DIVISOR_MASK 0xff #define CDIVG_DIVISOR_SHIFT 0 #define CAR_SUPER_CLK_DIVIDER_REGISTER() (IS_T13X ? \ CAR13_SUPER_CCLKG_DIVIDER : \ CAR_SUPER_CCLKG_DIVIDER) #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 THROT13_PSKIP_CTRL_CPU(vect) (THROT13_PSKIP_CTRL_LOW_CPU + \ (THROT13_OFFSET * vect)) #define THROT13_PSKIP_RAMP_CPU(vect) (THROT13_PSKIP_RAMP_LOW_CPU + \ (THROT13_OFFSET * vect)) #define THROT_PRIORITY_CTRL(throt) (THROT_PRIORITY_LITE + \ (THROT_OFFSET * throt)) #define THROT_DELAY_CTRL(throt) (THROT_DELAY_LITE + \ (THROT_OFFSET * throt)) #define ALARM_CFG(throt) (OC1_CFG + \ (ALARM_OFFSET * (throt - \ THROTTLE_OC1))) #define ALARM_CNT_THRESHOLD(throt) (OC1_CNT_THRESHOLD + \ (ALARM_OFFSET * (throt - \ THROTTLE_OC1))) #define ALARM_THROTTLE_PERIOD(throt) (OC1_THROTTLE_PERIOD + \ (ALARM_OFFSET * (throt - \ THROTTLE_OC1))) #define ALARM_ALARM_COUNT(throt) (OC1_ALARM_COUNT + \ (ALARM_OFFSET * (throt - \ THROTTLE_OC1))) #define ALARM_FILTER(throt) (OC1_FILTER + \ (ALARM_OFFSET * (throt - \ THROTTLE_OC1))) #define ALARM_STATS(throt) (OC1_STATS + \ (4 * (throt - THROTTLE_OC1))) #define THROT_DEPTH_DIVIDEND(depth) ((256 * (100 - (depth)) / 100) - 1) #define THROT_DEPTH(th, dp) { \ (th)->depth = (dp); \ (th)->dividend = THROT_DEPTH_DIVIDEND(dp); \ (th)->divisor = 255; \ (th)->duration = 0xff; \ (th)->step = 0xf; \ } #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))) #define IS_T11X (tegra_chip_id == TEGRA_CHIPID_TEGRA11) #define IS_T14X (tegra_chip_id == TEGRA_CHIPID_TEGRA14) #define IS_T12X (tegra_chip_id == TEGRA_CHIPID_TEGRA12) #define IS_T13X (tegra_chip_id == TEGRA_CHIPID_TEGRA13) static void __iomem *reg_soctherm_base = IOMEM(IO_ADDRESS(TEGRA_SOCTHERM_BASE)); static void __iomem *clk_reset_base = IOMEM(IO_ADDRESS(TEGRA_CLK_RESET_BASE)); static void __iomem *clk13_rst_base = IOMEM(IO_ADDRESS(TEGRA_CLK13_RESET_BASE)); static DEFINE_MUTEX(soctherm_suspend_resume_lock); static int soctherm_suspend(void); static int soctherm_resume(void); static struct soctherm_platform_data plat_data; /* * Remove this flag once this "driver" is structured as a platform driver and * the board files calls platform_device_register instead of directly calling * tegra11_soctherm_init(). See nvbug 1206311. */ static bool soctherm_init_platform_done; static bool read_hw_temp = true; static bool soctherm_suspended; static bool vdd_cpu_low_voltage; static bool vdd_core_low_voltage; static u32 tegra_chip_id; static struct clk *soctherm_clk; static struct clk *tsensor_clk; /** * soctherm_writel() - Writes a value to a SOC_THERM register * @value: The value to write * @reg: The register offset * * Writes the @value to @reg if the soctherm device is not suspended. */ static inline void soctherm_writel(u32 value, u32 reg) { if (!soctherm_suspended) __raw_writel(value, (void __iomem *) (reg_soctherm_base + reg)); } /** * soctherm_readl() - reads specified register from SOC_THERM IP block * @reg: register address to be read * * Return: 0 if SOC_THERM is suspended, else the value of the register */ static inline u32 soctherm_readl(u32 reg) { if (soctherm_suspended) return 0; return __raw_readl(reg_soctherm_base + reg); } /* XXX Temporary until CCROC accesses are split out */ static void clk_reset13_writel(u32 value, u32 reg) { BUG_ON(!IS_T13X); __raw_writel(value, clk13_rst_base + reg); __raw_readl(clk13_rst_base + reg); } /* XXX Temporary until CCROC accesses are split out */ static u32 clk_reset13_readl(u32 reg) { BUG_ON(!IS_T13X); return __raw_readl(clk13_rst_base + reg); } static inline void clk_reset_writel(u32 value, u32 reg) { if (IS_T13X) { __raw_writel(value, clk13_rst_base + reg); __raw_readl(clk13_rst_base + reg); } else __raw_writel(value, clk_reset_base + reg); } static inline u32 clk_reset_readl(u32 reg) { if (IS_T13X) return __raw_readl(clk13_rst_base + reg); else return __raw_readl(clk_reset_base + reg); } /** * temp_convert() - convert raw sensor readings to temperature * @cap: raw TSOSC count * @a: slope of count/temperature linear regression * @b: x-intercept of count/temperature linear regression * * This is a software version of what happens in the hardware when * temp_translate() is called. However, when the hardware does the conversion, * it cannot do it with the same precision that can be done with software. * * This function is not in use as long as @read_hw_temp is set to true, however * software temperature conversion could be used to monitor temperatures with a * higher degree of precision as they near a temperature threshold. * * Return: temperature in millicelsius. */ static inline long temp_convert(int cap, int a, int b) { cap *= a; cap >>= 10; cap += (b << 3); cap *= LOWER_PRECISION_FOR_CONV(500); cap /= 8; return cap; } /** * temp_translate_rev() - Translates the given temperature from two's * complement to the signed magnitude form used in SOC_THERM registers * @temp: The temperature to be translated * * The register value returned will have the following bit assignment: * 15:7 magnitude of temperature in (1/2 or 1 degree precision) centigrade * 0 the sign bit of the temperature * * This function is the inverse of the temp_translate() function * * Return: The register value. */ static inline u32 temp_translate_rev(long temp) { int sign; int low_bit; u32 lsb = 0; u32 abs = 0; u32 reg = 0; sign = (temp > 0 ? 1 : -1); low_bit = (sign > 0 ? 0 : 1); temp *= sign; /* high precision only */ if (!PRECISION_IS_LOWER()) { lsb = ((temp % 1000) > 0) ? 1 : 0; abs = (temp - 500 * lsb) / 1000; abs &= 0xff; reg = ((abs << 8) | (lsb << 7) | low_bit); } return reg; } #ifdef CONFIG_THERMAL static struct thermal_zone_device *soctherm_th_zones[THERM_SIZE]; #endif struct soctherm_oc_irq_chip_data { int irq_base; struct mutex irq_lock; /* serialize OC IRQs */ struct irq_chip irq_chip; struct irq_domain *domain; int irq_enable; }; static struct soctherm_oc_irq_chip_data soc_irq_cdata; 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 throt_names[] = { [THROTTLE_LIGHT] = "light", [THROTTLE_HEAVY] = "heavy", [THROTTLE_OC1] = "oc1", [THROTTLE_OC2] = "oc2", [THROTTLE_OC3] = "oc3", [THROTTLE_OC4] = "oc4", [THROTTLE_OC5] = "oc5", /* reserved */ }; static const char *const throt_dev_names[] = { [THROTTLE_DEV_CPU] = "CPU", [THROTTLE_DEV_GPU] = "GPU", }; 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 const struct soctherm_sensor default_t11x_sensor_params = { .tall = 16300, .tiddq = 1, .ten_count = 1, .tsample = 163, .tsamp_ate = 655, .pdiv = 10, .pdiv_ate = 10, }; static const struct soctherm_sensor default_t14x_sensor_params = { .tall = 16300, .tiddq = 1, .ten_count = 1, .tsample = 120, .tsamp_ate = 481, .pdiv = 8, .pdiv_ate = 8, }; /* Used for T124 and T132 */ static const struct soctherm_sensor default_t12x_sensor_params = { .tall = 16300, .tiddq = 1, .ten_count = 1, .tsample = 120, .tsamp_ate = 480, .pdiv = 8, .pdiv_ate = 8, }; static const unsigned long default_t11x_soctherm_clk_rate = 51000000; static const unsigned long default_t11x_tsensor_clk_rate = 500000; static const unsigned long default_t14x_soctherm_clk_rate = 51000000; static const unsigned long default_t14x_tsensor_clk_rate = 400000; /* TODO : finalize the default clk rate */ static const unsigned long default_t12x_soctherm_clk_rate = 51000000; static const unsigned long default_t12x_tsensor_clk_rate = 400000; /* SOC- OCx to theirt GPIO which is wakeup capable. This is T114 specific */ static int soctherm_ocx_to_wake_gpio[TEGRA_SOC_OC_IRQ_MAX] = { TEGRA_GPIO_PEE3, /* TEGRA_SOC_OC_IRQ_1 */ TEGRA_GPIO_INVALID, /* TEGRA_SOC_OC_IRQ_2 */ TEGRA_GPIO_INVALID, /* TEGRA_SOC_OC_IRQ_3 */ TEGRA_GPIO_PJ2, /* TEGRA_SOC_OC_IRQ_4 */ TEGRA_GPIO_INVALID, /* TEGRA_SOC_OC_IRQ_5 */ }; static int sensor2therm_a[TSENSE_SIZE]; static int sensor2therm_b[TSENSE_SIZE]; /** * div64_s64_precise() - wrapper for div64_s64() * @a: the dividend * @b: the divisor * * Implements division with fairly accurate rounding instead of truncation by * shifting the dividend to the left by 16 so that the quotient has a * much higher precision. * * Return: the quotient of a / b. */ 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; } /** * temp_translate() - Converts temperature * @readback: The value from a SOC_THERM sensor temperature * register * * Converts temperature from the format used in registers to a (signed) * long. This function is the inverse of temp_translate_rev(). * * Return: the translated temperature in millicelsius */ 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 /** * soctherm_has_mn_cpu_pskip_status() - does CPU use M,N values for pskip status? * * If the currently-running SoC reports the CPU thermal throttling * pulse skipper status with (M, N) values via SOC_THERM registers, * then return true; otherwise, return false. XXX Temporary - should * be replaced by autodetection or DT properties/compatible flags. * * Return: true if CPU thermal pulse-skipper M,N status values are available via * SOC_THERM, or false if not. */ static int soctherm_has_mn_cpu_pskip_status(void) { return IS_T11X || IS_T14X || IS_T12X; } /** * soctherm_get_mn_cpu_pskip_status() - read state of CPU thermal pulse skipper * @enabled: pointer to a u8: return 0 if the skipper is disabled, 1 if enabled * @sw_override: ptr to a u8: return 0 if sw override is disabled, 1 if enabled * @m: pointer to a u16 to return the current pulse skipper ratio numerator into * @n: pointer to a u16 to return the current pulse skipper ratio denominator to * * Read the current status of the thermal throttling pulse skipper * attached to the CPU clock, and return the status into the variables * pointed to by @enabled, @sw_override, @m, and @n. The M and N * values are not what is stored in the register bitfields, but * instead are the actual values used by the pulse skipper -- i.e., * they are the bitfield values _plus one_; they have valid ranges of * 1-256. This function is only defined for chips that report M,N * thermal throttling states * * Return: 0 upon success, -ENOTSUPP if called on a chip that uses * CPU-local (i.e., non-SOC_THERM) pulse-skipper status, or -EINVAL if * any of the arguments are NULL. */ int soctherm_get_mn_cpu_pskip_status(u8 *enabled, u8 *sw_override, u16 *m, u16 *n) { u32 v; if (!enabled || !m || !n || !sw_override) return -EINVAL; /* * XXX should be replaced with an earlier DT property read to * determine the GPU type (or GPU->SOC_THERM integration) in * use */ if (!soctherm_has_mn_cpu_pskip_status()) return -ENOTSUPP; v = soctherm_readl(CPU_PSKIP_STATUS); if (REG_GET(v, XPU_PSKIP_STATUS_ENABLED)) { *enabled = 1; *sw_override = REG_GET(v, XPU_PSKIP_STATUS_SW_OVERRIDE); *m = REG_GET(v, XPU_PSKIP_STATUS_M) + 1; *n = REG_GET(v, XPU_PSKIP_STATUS_N) + 1; } else { *enabled = 0; } return 0; } /** * soctherm_has_gpu_pskip_status() - is GPU pskip state readable via SOC_THERM? * * If the currently-running SoC reports the GPU thermal throttling * pulse skipper status via SOC_THERM registers, then return true; * otherwise, return false. XXX Temporary - should be replaced by * autodetection or DT properties/compatible flags. * * Return: true if GPU thermal pulse-skipper status is readable via * SOC_THERM, or false if not. */ static int soctherm_has_gpu_pskip_status(void) { return IS_T11X || IS_T14X; } /** * soctherm_get_gpu_pskip_status() - read state of the GPU thermal pulse skipper * @enabled: pointer to a u8: return 0 if the skipper is disabled, 1 if enabled * @sw_override: ptr to a u8: return 0 if sw override is disabled, 1 if enabled * @m: pointer to a u8 to return the current pulse skipper ratio numerator into * @n: pointer to a u8 to return the current pulse skipper ratio denominator to * * Read the current status of the thermal throttling pulse skipper * attached to the GPU clock, and return the status into the variables * pointed to by @enabled, @sw_override, @m, and @n. Note that the M * and N values are not what is stored in the register bitfields, but * instead are the actual values used by the pulse skipper -- i.e., they * are the bitfield values _plus one_; they have valid ranges of 1-256. * * Return: 0 upon success, -ENOTSUPP on chips with GPU-local * throttling status (e.g., T124, T132) or -EINVAL if any of the * arguments are NULL. */ int soctherm_get_gpu_pskip_status(u8 *enabled, u8 *sw_override, u16 *m, u16 *n) { u32 v; if (!enabled || !m || !n || !sw_override) return -EINVAL; /* * XXX should be replaced with an earlier DT property read to * determine the GPU type (or GPU->SOC_THERM integration) in * use */ if (!soctherm_has_gpu_pskip_status()) return -ENOTSUPP; v = soctherm_readl(GPU_PSKIP_STATUS); if (REG_GET(v, XPU_PSKIP_STATUS_ENABLED)) { *enabled = 1; *sw_override = REG_GET(v, XPU_PSKIP_STATUS_SW_OVERRIDE); *m = REG_GET(v, XPU_PSKIP_STATUS_M) + 1; *n = REG_GET(v, XPU_PSKIP_STATUS_N) + 1; } else { *enabled = 0; } return 0; } /** * enforce_temp_range() - check and enforce temperature range [min, max] * @trip_temp: The trip temperature to check * * Checks and enforces the permitted temperature range that SOC_THERM * HW can support with 8-bit registers to specify temperature. This is * done while taking care of precision. * * Return: The precsion adjusted capped temperature in millicelsius. */ static int enforce_temp_range(long trip_temp) { long temp = LOWER_PRECISION_FOR_TEMP(trip_temp); if (temp < MIN_LOW_TEMP) { pr_info("soctherm: trip_point temp %ld forced to %d\n", trip_temp, LOWER_PRECISION_FOR_CONV(MIN_LOW_TEMP)); temp = MIN_LOW_TEMP; } else if (temp > MAX_HIGH_TEMP) { pr_info("soctherm: trip_point temp %ld forced to %d\n", trip_temp, LOWER_PRECISION_FOR_CONV(MAX_HIGH_TEMP)); temp = MAX_HIGH_TEMP; } return temp; } /** * prog_hw_shutdown() - Configures the hardware to shut down the * system if a given sensor group reaches a given temperature * @trip_state: The trip information. Includes the temperature * at which a trip occurs. * @therm: Int specifying the sensor group. * Should be one of the following: * THERM_CPU, THERM_GPU, * THERM_MEM, or THERM_PPL. * * Sets the thermal trip threshold of the given sensor group * to be the trip temperature of @trip_state. * If this threshold is crossed, the hardware will shut down. * * Return: No return value (void). */ static inline void prog_hw_shutdown(struct thermal_trip_info *trip_state, int therm) { u32 r; int temp; /* Add 1'C to HW shutdown threshold so SW can try to shutdown first */ temp = trip_state->trip_temp + LOWER_PRECISION_FOR_CONV(1000); temp = enforce_temp_range(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, temp); } else if (therm == THERM_GPU) { r = REG_SET(r, THERMTRIP_GPU_EN, 1); r = REG_SET(r, THERMTRIP_GPUMEM_THRESH, temp); } else if (therm == THERM_PLL) { r = REG_SET(r, THERMTRIP_TSENSE_EN, 1); r = REG_SET(r, THERMTRIP_TSENSE_THRESH, temp); } else if (therm == THERM_MEM) { r = REG_SET(r, THERMTRIP_MEM_EN, 1); r = REG_SET(r, THERMTRIP_GPUMEM_THRESH, temp); } r = REG_SET(r, THERMTRIP_ANY_EN, 0); soctherm_writel(r, THERMTRIP); } /** * prog_hw_threshold() - updates hardware temperature threshold * of a particular trip point * @trip_state: setting of a trip point to use to update hardware threshold * @therm: soctherm_therm_id specifying the sensor group to update * @throt: soctherm_throttle_id indicating throttling level to update * * Configure sensor group @therm to engage a hardware throttling response at * the threshold indicated by @trip_state. */ static inline void prog_hw_threshold(struct thermal_trip_info *trip_state, int therm, int throt) { u32 r, reg_off; int temp; int cpu_throt, gpu_throt; temp = enforce_temp_range(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); if (throt == THROTTLE_LIGHT) { cpu_throt = CTL_LVL0_CPU0_CPU_THROT_LIGHT; gpu_throt = CTL_LVL0_CPU0_GPU_THROT_LIGHT; } else { cpu_throt = CTL_LVL0_CPU0_CPU_THROT_HEAVY; gpu_throt = CTL_LVL0_CPU0_GPU_THROT_HEAVY; if (throt != THROTTLE_HEAVY) pr_warn("soctherm: invalid throt %d - assuming HEAVY", throt); } r = soctherm_readl(reg_off); r = REG_SET(r, CTL_LVL0_CPU0_UP_THRESH, temp); r = REG_SET(r, CTL_LVL0_CPU0_DN_THRESH, temp); r = REG_SET(r, CTL_LVL0_CPU0_CPU_THROT, cpu_throt); r = REG_SET(r, CTL_LVL0_CPU0_GPU_THROT, gpu_throt); r = REG_SET(r, CTL_LVL0_CPU0_EN, 1); soctherm_writel(r, reg_off); } /** * soctherm_set_limits() - Configures a sensor group to raise interrupts outside * the given temperature range * @therm: ID of the sensor group * @lo_limit: The lowest temperature limit * @hi_limit: The highest temperature limit * * Configures sensor group @therm to raise an interrupt when temperature goes * above @hi_limit or below @lo_limit. */ static void soctherm_set_limits(enum soctherm_therm_id therm, long lo_limit, long hi_limit) { u32 r, reg_off; int rlo_limit, rhi_limit; rlo_limit = LOWER_PRECISION_FOR_TEMP(lo_limit) / 1000; rhi_limit = LOWER_PRECISION_FOR_TEMP(hi_limit) / 1000; reg_off = TS_THERM_REG_OFFSET(CTL_LVL0_CPU0, 0, therm); r = soctherm_readl(reg_off); r = REG_SET(r, CTL_LVL0_CPU0_DN_THRESH, rlo_limit); r = REG_SET(r, CTL_LVL0_CPU0_UP_THRESH, rhi_limit); r = REG_SET(r, CTL_LVL0_CPU0_EN, 1); soctherm_writel(r, reg_off); switch (therm) { case THERM_CPU: r = REG_SET(0, TH_INTR_POS_CD0, 1); r = REG_SET(r, TH_INTR_POS_CU0, 1); break; case THERM_GPU: r = REG_SET(0, TH_INTR_POS_GD0, 1); r = REG_SET(r, TH_INTR_POS_GU0, 1); break; case THERM_PLL: r = REG_SET(0, TH_INTR_POS_PD0, 1); r = REG_SET(r, TH_INTR_POS_PU0, 1); break; case THERM_MEM: r = REG_SET(0, TH_INTR_POS_MD0, 1); r = REG_SET(r, TH_INTR_POS_MU0, 1); break; default: r = 0; break; } soctherm_writel(r, TH_INTR_ENABLE); } /** * soctherm_update_zone() - Updates the given zone. * @zn: The number of the zone to be updated. * This number correlates to one of the following: * CPU, GPU, MEM, or PLL. * * Based on current temperature and the trip points associated with * this zone, update the temperature thresholds at which hardware will * generate interrupts. * * Return: Nothing is returned (void). */ static void soctherm_update_zone(int zn) { 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 = soctherm_th_zones[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) 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 and CRITICAL*/ 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, high_temp); } /** * soctherm_update() - updates all thermal zones * * Will not run if the board-specific data has not been initialized. Loops * through all of the thermal zones and makes sure that their high and low * temperature limits are updated. */ static void soctherm_update(void) { int i; if (!soctherm_init_platform_done) return; for (i = 0; i < THERM_SIZE; i++) { if (soctherm_th_zones[i] && soctherm_th_zones[i]->trips) soctherm_update_zone(i); } } /** * soctherm_hw_action_get_max_state() - gets the max state for cooling * devices associated with hardware throttling * @cdev: cooling device to get the state * @max_state: pointer where the maximum state will be written to * * Sets @max_state = 3. See soctherm_hw_action_get_cur_state. * * Return: 0 */ 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 = 3; /* bit 1: CPU bit 2: GPU */ return 0; } /** * soctherm_get_cpu_throt_state - read the current state of the CPU pulse skipper * @dividend: pulse skipper numerator value to test against (1-256) * @divisor: pulse skipper denominator value to test against (1-256) * @cur_state: ptr to the variable that the current throttle state is stored in * * Determine the current state of the CPU thermal throttling pulse * skipper, and if it's enabled and at its configured ending state, * set the appropriate 'enabled' bit in the variable pointed to by * @cur_state. This works on T114, T124, and T148 by comparing * @dividend and @divisor with the current state of the hardware - * though note that @dividend and @divisor must be the actual dividend * and divisor values. That is, they must be in 1-256 range, not the * 0-255 range used by the hardware bitfields. * * FIXME: For T132 switch to Denver:CCROC NV_THERM style status. Does * not currently work on T132. * * Return: 0 upon success, -ENOTSUPP on T12x and T13x, or -EINVAL if * the arguments are invalid or out of range. * */ static int soctherm_get_cpu_throt_state(u16 dividend, u16 divisor, unsigned long *cur_state) { u16 m, n; u8 enabled, sw_override; if (!cur_state || dividend == 0 || divisor == 0 || dividend > 256 || divisor > 256) return -EINVAL; if (soctherm_has_mn_cpu_pskip_status()) { soctherm_get_mn_cpu_pskip_status(&enabled, &sw_override, &m, &n); if (enabled && m == dividend && n == divisor) *cur_state |= (1 << THROTTLE_DEV_CPU); } else { pr_warn_once("CPU throttling status not yet available on this SoC\n"); return -EINVAL; } return 0; } /** * soctherm_get_gpu_throt_state - read the current state of the GPU pulse skipper * @dividend: pulse skipper numerator value to test against (1-256) * @divisor: pulse skipper denominator value to test against (1-256) * @cur_state: ptr to the variable that the current throttle state is stored in * * Determine the current state of the GPU thermal throttling pulse * skipper, and if it's enabled and at its configured ending state, * set the appropriate 'enabled' bit in the variable pointed to by * @cur_state. This works on T114 and T148 by comparing @dividend and * @divisor with the current state of the hardware - though note that * @dividend and @divisor must be the actual dividend and divisor * values. That is, they must be in 1-256 range, not the 0-255 range used * by the hardware bitfields. * * Unfortunately, on T12x and T13x, the GPU manages its own thermal * throttling, and does not report its state to the SOC_THERM IP * block. So on those chips, this function will return an error. * * Return: 0 upon success, -ENOTSUPP on T12x and T13x, or -EINVAL if * the arguments are invalid or out of range. */ static int soctherm_get_gpu_throt_state(u16 dividend, u16 divisor, unsigned long *cur_state) { u16 m, n; u8 enabled, sw_override; int r; if (!cur_state || dividend == 0 || divisor == 0 || dividend > 256 || divisor > 256) return -EINVAL; /* * XXX should be replaced with an earlier DT property read to * determine the GPU type (or GPU->SOC_THERM integration) in * use */ if (!soctherm_has_gpu_pskip_status()) return -ENOTSUPP; r = soctherm_get_gpu_pskip_status(&enabled, &sw_override, &m, &n); if (r) { WARN_ON(1); return r; } if (!enabled) return 0; *cur_state |= (m == dividend && n == divisor) ? (1 << THROTTLE_DEV_GPU) : 0; return r; } /** * soctherm_hw_action_get_cur_state() - get the current CPU/GPU throttling state * @cdev: ptr to the struct thermal_cooling_device associated with SOC_THERM * @cur_state: ptr to a variable to return the throttling state into * * Query the current state of the SOC_THERM cooling device represented * by @cdev, and return its current state into the variable pointed to * by @cur_state. Intended to be used as a thermal framework callback * function. * * 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; struct soctherm_throttle_dev *devs; int i, r; if (!trip_state) return 0; *cur_state = 0; if (trip_state->trip_type != THERMAL_TRIP_HOT) return 0; for (i = THROTTLE_LIGHT; i <= THROTTLE_HEAVY; i++) { if (!strnstr(trip_state->cdev_type, throt_names[i], THERMAL_NAME_LENGTH)) continue; devs = &plat_data.throttle[i].devs[THROTTLE_DEV_CPU]; if (devs->enable) soctherm_get_cpu_throt_state(devs->dividend + 1, devs->divisor + 1, cur_state); devs = &plat_data.throttle[i].devs[THROTTLE_DEV_GPU]; if (devs->enable) { r = soctherm_get_gpu_throt_state(devs->dividend + 1, devs->divisor + 1, cur_state); /* * On some chips, the GPU thermal throttling * status isn't reported back to the SOC_THERM * hardware. The ideal situation is for the * GPU driver to register its own cooling * device in that case; however, that code * isn't implemented AFAIK. On those chips, * Diwakar's preferred approach is for the GPU * throttling status bit to follow the CPU * throttling status bit, since that's the * vendor- recommended thermal configuration. * Diwakar notes: On Tegra12x OC5 is a * reserved alarm. Hence GPU 'PSKIP' state * always shows ON. The real status register * 'NV_THERM_CLK_STATUS' can't be read safely * [from this code - pjw]. So we mirror the * CPU status. */ if (r == -ENOTSUPP) if (*cur_state & (1 << THROTTLE_DEV_CPU)) *cur_state |= (1 << THROTTLE_DEV_GPU); } } 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 *soctherm_hw_critical_cdev; static struct thermal_cooling_device *soctherm_hw_heavy_cdev; static struct thermal_cooling_device *soctherm_hw_light_cdev; 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_suspend_get_max_state(struct thermal_cooling_device *cdev, unsigned long *max_state) { *max_state = 1; return 0; } static int soctherm_suspend_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *cur_state) { *cur_state = !soctherm_suspended; return 0; } /** * soctherm_suspend_set_cur_state() - Resumes or suspends soctherm * @cdev: Thermal cooling device. Currently not being used. * @cur_state: The current state * * Ensures that the SOC_THERM device is suspended or resumed to match * @cur_state. This function is passed to the thermal framework as part of a * cooling device. This is a workaround to suspend the SOC_THERM IP block, which * is only needed because this is not yet a device driver. Once this code is * converted to be a device driver, the soctherm_suspend implementation can * be removed * Return: 0 (success). */ static int soctherm_suspend_set_cur_state(struct thermal_cooling_device *cdev, unsigned long cur_state) { if (!cur_state != soctherm_suspended) { if (cur_state) soctherm_resume(); else soctherm_suspend(); } return 0; } static struct thermal_cooling_device_ops soctherm_suspend_ops = { .get_max_state = soctherm_suspend_get_max_state, .get_cur_state = soctherm_suspend_get_cur_state, .set_cur_state = soctherm_suspend_set_cur_state, }; /** * soctherm_bind() - Binds the given thermal zone's trip * points with the given cooling device. * @thz: The thermal zone device to be bound * @cdev: The cooling device to be bound * * If thermal sensor calibration data is missing from fuses, * the cooling devices are not bound. * * Based on platform-specific configuration associated with this * thermal zone, soctherm_bind() binds this cooling device to this * thermal zone at various trip points. * * soctherm_bind is called as a thermal_zone_device_ops bind function. * * Return: Returns 0 on successful binding. Returns 0 if passed an * invalid thermal zone argument, or improperly fused soctherm. * In the latter two cases, binding of the cooling device does not * occur. */ static int soctherm_bind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { int i; struct soctherm_therm *therm = thz->devdata; struct thermal_trip_info *trip_state; /* skip binding cooling devices on improperly fused soctherm */ if (tegra_fuse_calib_base_get_cp(NULL, NULL) < 0 || tegra_fuse_calib_base_get_ft(NULL, NULL) < 0) return 0; for (i = 0; i < therm->num_trips; i++) { trip_state = &therm->trips[i]; 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; } } return 0; } /** * soctherm_unbind() - unbinds cooling device from a thermal zone. * @thz: thermal zone to be dissociated with a cooling device * @cdev: a cooling device to be dissociated with a thermal zone * * Dissociates a given cooling device from a given thermal zone. * This function will go through every trip point and dissociate * cooling device from the thermal zone. * * Return: 0 */ static int soctherm_unbind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { int i; struct soctherm_therm *therm = thz->devdata; struct thermal_trip_info *trip_state; for (i = 0; i < therm->num_trips; i++) { trip_state = &therm->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; } /** * soctherm_get_temp() - gets the temperature for the given thermal zone * @thz: the thermal zone from which to get the temperature * @temp: a pointer to where the temperature will be stored * * Reads the sensor associated with the given thermal zone, converts the * reading to millicelcius, and places it into temp. This function is passed * to the thermal framework as a callback function when the zone is created and * registered. * * Return: 0 */ static int soctherm_get_temp(struct thermal_zone_device *thz, long *temp) { struct soctherm_therm *therm = thz->devdata; ptrdiff_t index = therm - plat_data.therm; u32 r, regv, shft, mask; enum soctherm_sense i, j; int tt, ti; 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_MEM: regv = TS_TEMP2; shft = TS_TEMP2_MEM_TEMP_SHIFT; mask = TS_TEMP2_MEM_TEMP_MASK; i = TSENSE_MEM0; j = TSENSE_MEM1; break; case THERM_PLL: default: /* if devdata has error, return PLL temp to be safe */ regv = TS_TEMP2; shft = TS_TEMP2_PLLX_TEMP_SHIFT; mask = TS_TEMP2_PLLX_TEMP_MASK; i = TSENSE_PLLX; j = TSENSE_PLLX; break; } if (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; } /** * soctherm_get_trip_type() - Gets the type of a given trip point * for a given thermal zone device. * @thz: The thermal zone device * @trip: The trip point index. * @type: The trip type. * * The trip type will be one of the following values: * THERMAL_TRIP_ACTIVE, THERMAL_TRIP_PASSIVE, THERMAL_TRIP_HOT, * THERMAL_TRIP_CRITICAL * * This function is passed to the thermal framework as a callback * for each of the SOC_THERM-related thermal zones * * Return: Returns 0 on success, -EINVAL when passed an invalid argument. */ static int soctherm_get_trip_type(struct thermal_zone_device *thz, int trip, enum thermal_trip_type *type) { struct soctherm_therm *therm = thz->devdata; *type = therm->trips[trip].trip_type; return 0; } /** * soctherm_get_trip_temp() - gets the threshold of a trip point from a zone * @thz: the thermal zone whose trip point temperature will be accessed * @trip: the index of the trip point * @temp: a pointer to where the temperature will be stored * * Reads the temperature threshold value of the specified trip point from the * specified thermal zone (in millicelsius) and places it into temp. It also * update's the zone's tripped flag. This function is passed to the thermal * framework as a callback function for each thermal zone. * * Return: 0 if success, otherwise %-EINVAL. */ static int soctherm_get_trip_temp(struct thermal_zone_device *thz, int trip, long *temp) { struct soctherm_therm *therm = thz->devdata; struct thermal_trip_info *trip_state; long trip_temp, zone_temp; trip_state = &therm->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; } /** * soctherm_set_trip_temp() - updates trip temperature * for a particular trip point * @thz: pointer to thermal_zone_device to update trip temperature * @trip: index value of thermal_trip_info in soctherm_therm->trips * @temp: value for new temperature * * Updates both the software data structure and the hardware threshold * for a trip point. This function is passed to the thermal framework * as a callback function for each of the thermal zone. * * Return: 0 if successful else %-EINVAL */ static int soctherm_set_trip_temp(struct thermal_zone_device *thz, int trip, long temp) { struct soctherm_therm *therm = thz->devdata; struct thermal_trip_info *trip_state; ptrdiff_t index = therm - plat_data.therm; long rem; trip_state = &therm->trips[trip]; trip_state->trip_temp = enforce_temp_range(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", thz->id, 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' */ thermal_notify_framework(thz, trip); /* Reprogram HW thermtrip */ if (trip_state->trip_type == THERMAL_TRIP_CRITICAL) prog_hw_shutdown(trip_state, index); return 0; } /** * soctherm_get_crit_temp() - Gets critical temperature of a thermal zone * @tzd: The pointer to thermal zone device * @temp: The pointer to the temperature * * Iterates through the list of thermal trips for a given @thz, and looks for * its critical temperature point @temp to cause a shutdown. * * Return: 0 if it is able to find a critical temperature point and stores it * into the variable pointed by the address in @temp; Otherwise, return -EINVAL. */ static int soctherm_get_crit_temp(struct thermal_zone_device *thz, long *temp) { int i; struct soctherm_therm *therm = thz->devdata; struct thermal_trip_info *trip_state; for (i = 0; i < therm->num_trips; i++) { trip_state = &therm->trips[i]; if (trip_state->trip_type != THERMAL_TRIP_CRITICAL) continue; *temp = trip_state->trip_temp; return 0; } return -EINVAL; } /** * soctherm_get_trend() - Gets the thermal trend for a given * thermal zone device * @thz: The thermal zone device whose trend is being obtained * @trip: The trip point number * @trend: The thermal trend * * This function is passed to the thermal framework as a callback * for SOC_THERM's thermal zone devices * * The trend will be one of the following: * THERMAL_TREND_STABLE: the temperature is stable * THERMAL_TREND_RAISING: the temperature is increasing * THERMAL_TREND_DROPPING: the temperature is decreasing * THERMAL_TREND_RAISE_FULL: apply the highest cooling action * THERMAL_TREND_DROP_FULL: apply the lowest cooling action * * If the trip type of the trip point of the thermal zone device is * THERMAL_TRIP_ACTIVE, then the thermal trend is THERMAL_TREND_RAISING. * Otherwise, if the device's temperature is higher than its trip * temperature, the trend is THERMAL_TREND_RAISING. If the device's * temperature is lower, the trend is THERMAL_TREND_DROPPING. Otherwise * the trend is stable. * * Return: 0 on success. Returns -EINVAL if the function was * passed an invalid argument. */ static int soctherm_get_trend(struct thermal_zone_device *thz, int trip, enum thermal_trend *trend) { struct soctherm_therm *therm = thz->devdata; struct thermal_trip_info *trip_state; long trip_temp; trip_state = &therm->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, }; /** * soctherm_hot_cdev_register() - registers cooling devices * associated with hardware throttling. * @i: soctherm_therm_id index of the sensor group * @trip: index of thermal_trip_info in soctherm_therm->trips * * As indicated by platform configuration data, registers with * the thermal framework two cooling devices representing * SOC_THERM's hardware throttling capability associated with * sensor group @i * * These cooling devices are special. To function properly, they must be * bound (with a single trip point) to the thermal zone associated with * the same sensor group. * * Setting the trip point temperature leads to an adjustment of the * hardware throttling temperature threshold. Examining the cooling * device's cur_state indicates whether hardware throttling is active. */ static void __init soctherm_hot_cdev_register(int i, int trip) { struct soctherm_therm *therm; int k; therm = &plat_data.therm[i]; for (k = 0; k < THROTTLE_SIZE; k++) { if ((therm2dev[i] == THROTTLE_DEV_NONE) || (!plat_data.throttle[k].devs[therm2dev[i]].enable)) continue; if ((strnstr(therm->trips[trip].cdev_type, "oc1", THERMAL_NAME_LENGTH) && k == THROTTLE_OC1) || (strnstr(therm->trips[trip].cdev_type, "oc2", THERMAL_NAME_LENGTH) && k == THROTTLE_OC2) || (strnstr(therm->trips[trip].cdev_type, "oc3", THERMAL_NAME_LENGTH) && k == THROTTLE_OC3) || (strnstr(therm->trips[trip].cdev_type, "oc4", THERMAL_NAME_LENGTH) && k == THROTTLE_OC4)) continue; if (strnstr(therm->trips[trip].cdev_type, "heavy", THERMAL_NAME_LENGTH) && k == THROTTLE_HEAVY && !soctherm_hw_heavy_cdev) { soctherm_hw_heavy_cdev = thermal_cooling_device_register( therm->trips[trip].cdev_type, &therm->trips[trip], &soctherm_hw_action_ops); continue; } if (strnstr(therm->trips[trip].cdev_type, "light", THERMAL_NAME_LENGTH) && k == THROTTLE_LIGHT && !soctherm_hw_light_cdev) { soctherm_hw_light_cdev = thermal_cooling_device_register( therm->trips[trip].cdev_type, &therm->trips[trip], &soctherm_hw_action_ops); continue; } } } /** * soctherm_thermal_sys_init() - initializes the SOC_THERM thermal system * * After the board-specific data has been initalized, this creates a thermal * zone device for each enabled sensor and each enabled sensor group. * It also creates a cooling zone device for each enabled thermal zone that has * a critical trip point. It enables the suspend feature if no over-current * alarms are enabled. * * Once all of the thermal zones have been registered, it runs * soctherm_update(), which sets high and low temperature thresholds. * * Runs at kernel boot-time. * * Return: 0 */ static int __init soctherm_thermal_sys_init(void) { char name[THERMAL_NAME_LENGTH]; struct soctherm_therm *therm; bool oc_en = false; int i, j; if (!soctherm_init_platform_done) return 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++) { switch (therm->trips[j].trip_type) { case THERMAL_TRIP_CRITICAL: if (soctherm_hw_critical_cdev) break; soctherm_hw_critical_cdev = thermal_cooling_device_register( therm->trips[j].cdev_type, &therm->trips[j], &soctherm_hw_action_ops); break; case THERMAL_TRIP_HOT: soctherm_hot_cdev_register(i, j); break; case THERMAL_TRIP_PASSIVE: case THERMAL_TRIP_ACTIVE: break; /* done elsewhere */ } } snprintf(name, THERMAL_NAME_LENGTH, "%s-therm", therm_names[i]); soctherm_th_zones[i] = thermal_zone_device_register( name, therm->num_trips, (1ULL << therm->num_trips) - 1, therm, &soctherm_ops, therm->tzp, therm->passive_delay, 0); for (j = THROTTLE_OC1; !oc_en && j < THROTTLE_SIZE; j++) if ((therm2dev[i] != THROTTLE_DEV_NONE) && (plat_data.throttle[j].devs[therm2dev[i]].enable)) oc_en = true; } /* do not enable suspend feature if any OC alarms are enabled */ if (!oc_en) thermal_cooling_device_register("suspend_soctherm", NULL, &soctherm_suspend_ops); else pr_warn("soctherm: Suspend feature CANNOT be enabled %s\n", "when any OC alarm is enabled"); soctherm_update(); return 0; } module_init(soctherm_thermal_sys_init); #else static void soctherm_update_zone(int zn) { } static void soctherm_update(void) { } #endif /** * soctherm_thermal_thread_func() - Handles a thermal interrupt request * @irq: The interrupt number being requested; not used * @arg: Opaque pointer to an argument; not used * * Clears the interrupt status register if there are expected * interrupt bits set. * The interrupt(s) are then handled by updating the corresponding * thermal zones. * * An error is logged if any unexpected interrupt bits are set. * * Disabled interrupts are re-enabled. * * Return: %IRQ_HANDLED. Interrupt was handled and no further processing * is needed. */ static irqreturn_t soctherm_thermal_thread_func(int irq, void *arg) { u32 st, ex = 0, cp = 0, gp = 0, pl = 0; st = soctherm_readl(TH_INTR_STATUS); /* deliberately clear expected interrupts handled in SW */ cp |= REG_GET_BIT(st, TH_INTR_POS_CD0); cp |= REG_GET_BIT(st, TH_INTR_POS_CU0); ex |= cp; gp |= REG_GET_BIT(st, TH_INTR_POS_GD0); gp |= REG_GET_BIT(st, TH_INTR_POS_GU0); ex |= gp; pl |= REG_GET_BIT(st, TH_INTR_POS_PD0); pl |= REG_GET_BIT(st, TH_INTR_POS_PU0); ex |= pl; if (ex) { soctherm_writel(ex, TH_INTR_STATUS); st &= ~ex; if (cp) soctherm_update_zone(THERM_CPU); if (gp) soctherm_update_zone(THERM_GPU); if (pl) soctherm_update_zone(THERM_PLL); } /* deliberately ignore expected interrupts NOT handled in SW */ ex |= REG_GET_BIT(st, TH_INTR_POS_MD0); ex |= REG_GET_BIT(st, TH_INTR_POS_MU0); ex |= REG_GET_BIT(st, TH_INTR_POS_CD1); ex |= REG_GET_BIT(st, TH_INTR_POS_CU1); ex |= REG_GET_BIT(st, TH_INTR_POS_CD2); ex |= REG_GET_BIT(st, TH_INTR_POS_CU2); ex |= REG_GET_BIT(st, TH_INTR_POS_CD3); ex |= REG_GET_BIT(st, TH_INTR_POS_CU3); ex |= REG_GET_BIT(st, TH_INTR_POS_GD1); ex |= REG_GET_BIT(st, TH_INTR_POS_GU1); ex |= REG_GET_BIT(st, TH_INTR_POS_GD2); ex |= REG_GET_BIT(st, TH_INTR_POS_GU2); ex |= REG_GET_BIT(st, TH_INTR_POS_GD3); ex |= REG_GET_BIT(st, TH_INTR_POS_GU3); ex |= REG_GET_BIT(st, TH_INTR_POS_PD1); ex |= REG_GET_BIT(st, TH_INTR_POS_PU1); ex |= REG_GET_BIT(st, TH_INTR_POS_PD2); ex |= REG_GET_BIT(st, TH_INTR_POS_PU2); ex |= REG_GET_BIT(st, TH_INTR_POS_PD3); ex |= REG_GET_BIT(st, TH_INTR_POS_PU3); ex |= REG_GET_BIT(st, TH_INTR_POS_MD1); ex |= REG_GET_BIT(st, TH_INTR_POS_MU1); ex |= REG_GET_BIT(st, TH_INTR_POS_MD2); ex |= REG_GET_BIT(st, TH_INTR_POS_MU2); ex |= REG_GET_BIT(st, TH_INTR_POS_MD3); ex |= REG_GET_BIT(st, TH_INTR_POS_MU3); st &= ~ex; if (st) { /* Whine about any other unexpected INTR bits still set */ pr_err("soctherm: Ignored unexpected INTRs 0x%08x\n", st); soctherm_writel(st, TH_INTR_STATUS); } return IRQ_HANDLED; } /** * soctherm_oc_intr_enable() - Enables the soctherm over-current interrupt * @alarm: The soctherm throttle id * @enable: Flag indicating enable the soctherm over-current * interrupt or disable it * * Enables a specific over-current pins @alarm to raise an interrupt if the flag * is set and the alarm corresponds to OC1, OC2, OC3, or OC4. */ static inline void soctherm_oc_intr_enable(enum soctherm_throttle_id alarm, bool enable) { u32 r; if (!enable) return; r = soctherm_readl(OC_INTR_ENABLE); switch (alarm) { case THROTTLE_OC1: r = REG_SET(r, OC_INTR_POS_OC1, 1); break; case THROTTLE_OC2: r = REG_SET(r, OC_INTR_POS_OC2, 1); break; case THROTTLE_OC3: r = REG_SET(r, OC_INTR_POS_OC3, 1); break; case THROTTLE_OC4: r = REG_SET(r, OC_INTR_POS_OC4, 1); break; default: r = 0; break; } soctherm_writel(r, OC_INTR_ENABLE); } /** * soctherm_handle_alarm() - Handles soctherm alarms * @alarm: The soctherm throttle id * * "Handles" over-current alarms (OC1, OC2, OC3, and OC4) by printing * a warning or informative message. * * Return: -EINVAL for @alarm = THROTTLE_OC3, otherwise 0 (success). */ static int soctherm_handle_alarm(enum soctherm_throttle_id alarm) { int rv = -EINVAL; switch (alarm) { case THROTTLE_OC1: pr_debug("soctherm: Successfully handled OC1 alarm\n"); /* add OC1 alarm handling code here */ rv = 0; break; case THROTTLE_OC2: pr_info("soctherm: Successfully handled OC2 alarm\n"); /* TODO: add OC2 alarm handling code here */ rv = 0; break; case THROTTLE_OC3: pr_warn("soctherm: Unexpected OC3 alarm\n"); /* add OC3 alarm handling code here */ break; case THROTTLE_OC4: pr_debug("soctherm: Successfully handled OC4 alarm\n"); /* TODO: add OC4 alarm handling code here */ rv = 0; break; default: break; } if (rv) pr_err("soctherm: ERROR in handling %s alarm\n", throt_names[alarm]); return rv; } /** * soctherm_edp_thread_func() - log an over-current interrupt request * @irq: OC irq number. Currently not being used. See description * @arg: a void pointer for callback, currently not being used * * Over-current events are handled in hardware. This function is called to log * and handle any OC events that happened. Additionally, it checks every * over-current interrupt registers for registers are set but * was not expected (i.e. any discrepancy in interrupt status) by the function, * the discrepancy will logged. * * Return: %IRQ_HANDLED */ static irqreturn_t soctherm_edp_thread_func(int irq, void *arg) { u32 st, ex, oc1, oc2, oc3, oc4; st = soctherm_readl(OC_INTR_STATUS); /* deliberately clear expected interrupts handled in SW */ oc1 = REG_GET_BIT(st, OC_INTR_POS_OC1); oc2 = REG_GET_BIT(st, OC_INTR_POS_OC2); oc3 = REG_GET_BIT(st, OC_INTR_POS_OC3); oc4 = REG_GET_BIT(st, OC_INTR_POS_OC4); ex = oc1 | oc2 | oc3 | oc4; if (ex) { soctherm_writel(st, OC_INTR_STATUS); st &= ~ex; if (oc1 && !soctherm_handle_alarm(THROTTLE_OC1)) soctherm_oc_intr_enable(THROTTLE_OC1, true); if (oc2 && !soctherm_handle_alarm(THROTTLE_OC2)) soctherm_oc_intr_enable(THROTTLE_OC2, true); if (oc3 && !soctherm_handle_alarm(THROTTLE_OC3)) soctherm_oc_intr_enable(THROTTLE_OC3, true); if (oc4 && !soctherm_handle_alarm(THROTTLE_OC4)) soctherm_oc_intr_enable(THROTTLE_OC4, true); if (oc1 && soc_irq_cdata.irq_enable & BIT(0)) handle_nested_irq( irq_find_mapping(soc_irq_cdata.domain, 0)); if (oc2 && soc_irq_cdata.irq_enable & BIT(1)) handle_nested_irq( irq_find_mapping(soc_irq_cdata.domain, 1)); if (oc3 && soc_irq_cdata.irq_enable & BIT(2)) handle_nested_irq( irq_find_mapping(soc_irq_cdata.domain, 2)); if (oc4 && soc_irq_cdata.irq_enable & BIT(3)) handle_nested_irq( irq_find_mapping(soc_irq_cdata.domain, 3)); } if (st) { pr_err("soctherm: Ignored unexpected OC ALARM 0x%08x\n", st); soctherm_writel(st, OC_INTR_STATUS); } return IRQ_HANDLED; } /** * soctherm_thermal_isr() - thermal interrupt request handler * @irq: Interrupt request number * @arg: Not used. * * Reads the thermal interrupt status and then disables any asserted * interrupts. The thread woken by this isr services the asserted * interrupts and re-enables them. * * Return: %IRQ_WAKE_THREAD */ static irqreturn_t soctherm_thermal_isr(int irq, void *arg) { u32 r; r = soctherm_readl(TH_INTR_STATUS); soctherm_writel(r, TH_INTR_DISABLE); return IRQ_WAKE_THREAD; } /** * soctherm_edp_isr() - Disables any active interrupts * @irq: The interrupt request number * @arg: Opaque pointer to an argument * * Writes to the OC_INTR_DISABLE register the over current interrupt status, * masking any asserted interrupts. Doing this prevents the same interrupts * from triggering this isr repeatedly. The thread woken by this isr will * handle asserted interrupts and subsequently unmask/re-enable them. * * The OC_INTR_DISABLE register indicates which OC interrupts * have been disabled. * * Return: %IRQ_WAKE_THREAD, handler requests to wake the handler thread */ static irqreturn_t soctherm_edp_isr(int irq, void *arg) { u32 r; r = soctherm_readl(OC_INTR_STATUS); soctherm_writel(r, OC_INTR_DISABLE); return IRQ_WAKE_THREAD; } /** * throttlectl_cpu_mn() - program CPU pulse skipper configuration * @throt: soctherm_throttle_id describing the level of throttling * * Pulse skippers are used to throttle clock frequencies. This * function programs the pulse skippers based on @throt and platform * data. This function is used for CPUs that have "remote" pulse * skipper control, e.g., the CPU pulse skipper is controlled by the * SOC_THERM IP block. (SOC_THERM is located outside the CPU * complex.) * * Return: boolean true if HW was programmed, or false if the desired * configuration is not supported. */ static bool throttlectl_cpu_mn(enum soctherm_throttle_id throt) { u32 r; struct soctherm_throttle *data = &plat_data.throttle[throt]; struct soctherm_throttle_dev *dev = &data->devs[THROTTLE_DEV_CPU]; if (!dev->enable) return false; r = soctherm_readl(THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, dev->enable); r = REG_SET(r, THROT_PSKIP_CTRL_DIVIDEND, dev->dividend); r = REG_SET(r, THROT_PSKIP_CTRL_DIVISOR, dev->divisor); soctherm_writel(r, THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); r = soctherm_readl(THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); r = REG_SET(r, THROT_PSKIP_RAMP_DURATION, dev->duration); r = REG_SET(r, THROT_PSKIP_RAMP_STEP, dev->step); soctherm_writel(r, THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); return true; } /** * throttlectl_cpu_level() - program CPU pulse skipper configuration * @throt: soctherm_throttle_id describing the level of throttling * * Pulse skippers are used to throttle clock frequencies. This * function programs the pulse skippers based on @throt and platform * data. This function is used on SoCs which have CPU-local pulse * skipper control, such as T13x. It programs soctherm's interface to * Denver:CCROC NV_THERM in terms of Low, Medium and Heavy throttling * vectors. PSKIP_BYPASS mode is set as required per HW spec. * * It's also necessary to set up the CPU-local NV_THERM instance with * the M/N values desired for each level. This function does this * also, although it should be handled by a separate driver. * * Return: boolean true if HW was programmed, or false if the desired * configuration is not supported. */ static bool throttlectl_cpu_level(enum soctherm_throttle_id throt) { u32 r, throt_vect; int throt_level; struct soctherm_throttle *data = &plat_data.throttle[throt]; struct soctherm_throttle_dev *dev = &data->devs[THROTTLE_DEV_CPU]; if (!dev->enable) return false; /* Denver:CCROC NV_THERM interface N:3 Mapping */ if (!strcmp(dev->throttling_depth, "heavy_throttling")) { throt_level = THROT_LEVEL_HVY; throt_vect = THROT_VECT_HVY; } else if (!strcmp(dev->throttling_depth, "medium_throttling")) { throt_level = THROT_LEVEL_MED; throt_vect = THROT_VECT_MED; } else if (!strcmp(dev->throttling_depth, "low_throttling")) { throt_level = THROT_LEVEL_LOW; throt_vect = THROT_VECT_LOW; } else { throt_level = THROT_LEVEL_NONE; throt_vect = THROT_VECT_NONE; } if (dev->depth) THROT_DEPTH(dev, dev->depth); r = soctherm_readl(THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, dev->enable); /* setup throttle vector in soctherm register */ r = REG_SET(r, THROT_PSKIP_CTRL_VECT_CPU, throt_vect); r = REG_SET(r, THROT_PSKIP_CTRL_VECT2_CPU, throt_vect); soctherm_writel(r, THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); /* bypass sequencer in soc_therm as it is programmed in ccroc */ r = REG_SET(0, THROT_PSKIP_RAMP_SEQ_BYPASS_MODE, 1); soctherm_writel(r, THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); if (throt_level == THROT_LEVEL_NONE) return true; /* setup PSKIP in ccroc nv_therm registers */ r = clk_reset13_readl(THROT13_PSKIP_RAMP_CPU(throt_level)); r = REG_SET(r, THROT_PSKIP_RAMP_DURATION, dev->duration); r = REG_SET(r, THROT_PSKIP_RAMP_STEP, dev->step); clk_reset13_writel(r, THROT13_PSKIP_RAMP_CPU(throt_level)); r = clk_reset13_readl(THROT13_PSKIP_CTRL_CPU(throt_level)); r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, dev->enable); r = REG_SET(r, THROT_PSKIP_CTRL_DIVIDEND, dev->dividend); r = REG_SET(r, THROT_PSKIP_CTRL_DIVISOR, dev->divisor); clk_reset13_writel(r, THROT13_PSKIP_CTRL_CPU(throt_level)); return true; } /** * throttlectl_gpu_gk20a_nv_therm_style() - programs GK20a NV_THERM config * @dev device struct pointer to GPU device * @throt soctherm_throttle_id describing the level of throttling * * This function programs soctherm's interface to GK20a NV_THERM in * terms of Low, Medium and Heavy throttling preset levels. * * Return: boolean true if HW was programmed */ static bool throttlectl_gpu_gk20a_nv_therm_style( struct soctherm_throttle_dev *dev, enum soctherm_throttle_id throt) { u32 r, throt_vect; /* gk20a nv_therm interface N:3 Mapping */ if (!strcmp(dev->throttling_depth, "heavy_throttling")) throt_vect = THROT_VECT_HVY; else if (!strcmp(dev->throttling_depth, "medium_throttling")) throt_vect = THROT_VECT_MED; else throt_vect = THROT_VECT_LOW; r = soctherm_readl(THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU)); r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, dev->enable); r = REG_SET(r, THROT_PSKIP_CTRL_VECT_GPU, throt_vect); soctherm_writel(r, THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU)); r = soctherm_readl(THROT_PSKIP_RAMP(throt, THROTTLE_DEV_GPU)); r = REG_SET(r, THROT_PSKIP_RAMP_SEQ_BYPASS_MODE, 1); soctherm_writel(r, THROT_PSKIP_RAMP(throt, THROTTLE_DEV_GPU)); return true; } /** * throttlectl_gpu() - programs GPU pulse skippers' configuration * @throt soctherm_throttle_id describing the level of throttling * * Pulse skippers are used to throttle clock frequencies. * This function programs the pulse skippers based on @throt and platform data. * * Return: boolean true if HW was programmed */ static bool throttlectl_gpu(enum soctherm_throttle_id throt) { u32 r; struct soctherm_throttle *data = &plat_data.throttle[throt]; struct soctherm_throttle_dev *dev = &data->devs[THROTTLE_DEV_GPU]; if (!dev->enable) return false; if (IS_T12X || IS_T13X) return throttlectl_gpu_gk20a_nv_therm_style(dev, throt); if (dev->depth) THROT_DEPTH(dev, dev->depth); r = soctherm_readl(THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU)); r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, dev->enable); r = REG_SET(r, THROT_PSKIP_CTRL_DIVIDEND, dev->dividend); r = REG_SET(r, THROT_PSKIP_CTRL_DIVISOR, dev->divisor); soctherm_writel(r, THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU)); r = soctherm_readl(THROT_PSKIP_RAMP(throt, THROTTLE_DEV_GPU)); r = REG_SET(r, THROT_PSKIP_RAMP_DURATION, dev->duration); r = REG_SET(r, THROT_PSKIP_RAMP_STEP, dev->step); soctherm_writel(r, THROT_PSKIP_RAMP(throt, THROTTLE_DEV_GPU)); return true; } /** * soctherm_throttle_program() - programs pulse skippers' configuration * @throt soctherm_throttle_id describing the level of throttling * * Pulse skippers are used to throttle clock frequencies. * This function programs the pulse skippers based on @throt and platform data. * * Return: Nothing is returned (void). */ static void soctherm_throttle_program(enum soctherm_throttle_id throt) { u32 r; bool throt_enable; struct soctherm_throttle *data = &plat_data.throttle[throt]; throt_enable = (IS_T13X) ? throttlectl_cpu_level(throt) : throttlectl_cpu_mn(throt); throt_enable |= throttlectl_gpu(throt); r = REG_SET(0, THROT_PRIORITY_LITE_PRIO, data->priority); soctherm_writel(r, THROT_PRIORITY_CTRL(throt)); r = REG_SET(0, THROT_DELAY_LITE_DELAY, 0); soctherm_writel(r, THROT_DELAY_CTRL(throt)); 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); } /* ----- configure reserved OC5 alarm ----- */ if (throt == THROTTLE_OC5) { r = soctherm_readl(ALARM_CFG(throt)); r = REG_SET(r, OC1_CFG_THROTTLE_MODE, BRIEF); r = REG_SET(r, OC1_CFG_ALARM_POLARITY, 0); r = REG_SET(r, OC1_CFG_EN_THROTTLE, 1); soctherm_writel(r, ALARM_CFG(throt)); r = REG_SET(r, OC1_CFG_ALARM_POLARITY, 1); soctherm_writel(r, ALARM_CFG(throt)); r = REG_SET(r, OC1_CFG_ALARM_POLARITY, 0); soctherm_writel(r, ALARM_CFG(throt)); r = REG_SET(r, THROT_PSKIP_CTRL_DIVIDEND, 0); r = REG_SET(r, THROT_PSKIP_CTRL_DIVISOR, 0); r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, 1); soctherm_writel(r, THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU)); r = soctherm_readl(THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); r = REG_SET(r, THROT_PSKIP_RAMP_DURATION, 0xff); r = REG_SET(r, THROT_PSKIP_RAMP_STEP, 0xf); soctherm_writel(r, THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU)); r = soctherm_readl(THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU)); r = REG_SET(r, THROT_PSKIP_CTRL_ENABLE, 1); soctherm_writel(r, THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU)); r = REG_SET(0, THROT_PRIORITY_LITE_PRIO, 1); soctherm_writel(r, THROT_PRIORITY_CTRL(throt)); return; } if (!throt_enable || (throt < THROTTLE_OC1)) return; /* ----- configure other OC alarms ----- */ if (!(data->throt_mode == BRIEF || data->throt_mode == STICKY)) pr_warn("soctherm: Invalid throt_mode in %s\n", throt_names[throt]); r = soctherm_readl(ALARM_CFG(throt)); r = REG_SET(r, OC1_CFG_HW_RESTORE, 1); r = REG_SET(r, OC1_CFG_PWR_GOOD_MASK, data->pgmask); r = REG_SET(r, OC1_CFG_THROTTLE_MODE, data->throt_mode); r = REG_SET(r, OC1_CFG_ALARM_POLARITY, data->polarity); r = REG_SET(r, OC1_CFG_EN_THROTTLE, 1); soctherm_writel(r, ALARM_CFG(throt)); soctherm_oc_intr_enable(throt, data->intr); soctherm_writel(data->period, ALARM_THROTTLE_PERIOD(throt)); /* usec */ soctherm_writel(data->alarm_cnt_threshold, ALARM_CNT_THRESHOLD(throt)); if (data->alarm_filter) soctherm_writel(data->alarm_filter, ALARM_FILTER(throt)); else soctherm_writel(0xffffffff, ALARM_FILTER(throt)); } /** * soctherm_tsense_program() - Configure sensor timing parameters based on * chip-specific data. * * @sensor: The temperature sensor. This corresponds to one of the * four CPU sensors, one of the two memory * sensors, or the GPU or PLLX sensor. * @data: Information regarding a sensor. This comes from the platform * data * * This function is called during initialization. It sets two CPU thermal sensor * configuration registers (TS_CPU0_CONFIG0 and TS_CPU0_CONFIG1) * to contain the given chip-specific sensor's configuration data. * * The configuration data affects the sensor's temperature capturing. * * Return: Nothing is returned (void). */ 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)); } /** * soctherm_clk_init() - Initialize SOC_THERM related clocks. * * The initialization will map clock aliases for SOC_THERM and TSENSE * and set their clock rates based on chip-specific defaults or * any platform-specific overrides. * * Return: 0 if successful else %-EINVAL if initialization failed */ static int __init soctherm_clk_init(void) { unsigned long default_soctherm_clk_rate; unsigned long default_tsensor_clk_rate; soctherm_clk = clk_get_sys("soc_therm", NULL); tsensor_clk = clk_get_sys("tegra-tsensor", NULL); if (IS_ERR(tsensor_clk) || IS_ERR(soctherm_clk)) { clk_put(soctherm_clk); clk_put(tsensor_clk); soctherm_clk = NULL; tsensor_clk = NULL; return -EINVAL; } /* initialize default clock rates */ if (IS_T11X) { default_soctherm_clk_rate = default_t11x_soctherm_clk_rate; default_tsensor_clk_rate = default_t11x_tsensor_clk_rate; } else if (IS_T14X) { default_soctherm_clk_rate = default_t14x_soctherm_clk_rate; default_tsensor_clk_rate = default_t14x_tsensor_clk_rate; } else if ((IS_T12X || IS_T13X)) { default_soctherm_clk_rate = default_t12x_soctherm_clk_rate; default_tsensor_clk_rate = default_t12x_tsensor_clk_rate; } else { BUG(); } plat_data.soctherm_clk_rate = plat_data.soctherm_clk_rate ?: default_soctherm_clk_rate; plat_data.tsensor_clk_rate = plat_data.tsensor_clk_rate ?: default_tsensor_clk_rate; if (clk_get_rate(soctherm_clk) != plat_data.soctherm_clk_rate) if (clk_set_rate(soctherm_clk, plat_data.soctherm_clk_rate)) return -EINVAL; if (clk_get_rate(tsensor_clk) != plat_data.tsensor_clk_rate) if (clk_set_rate(tsensor_clk, plat_data.tsensor_clk_rate)) return -EINVAL; return 0; } /** * soctherm_clk_enable() - enables and disables the clocks * @enable: whether the clocks should be enabled or disabled * * Enables the SOC_THERM and thermal sensor clocks when SOC_THERM * is initialized. * * Return: 0 if successful, %-EINVAL if either clock hasn't been initialized. */ static int soctherm_clk_enable(bool enable) { if (soctherm_clk == NULL || tsensor_clk == NULL) return -EINVAL; if (enable) { clk_enable(soctherm_clk); clk_enable(tsensor_clk); } else { clk_disable(soctherm_clk); clk_disable(tsensor_clk); } return 0; } /** * soctherm_fuse_read_calib_base() - Calculates calibration base temperature * * Calculates the nominal temperature used for thermal sensor calibration * based on chip type and the value in fuses. * * Return: 0 (success), otherwise -EINVAL. */ static int soctherm_fuse_read_calib_base(void) { s32 calib_cp, calib_ft; s32 nominal_calib_cp, nominal_calib_ft; if (tegra_fuse_calib_base_get_cp(&fuse_calib_base_cp, &calib_cp) < 0 || tegra_fuse_calib_base_get_ft(&fuse_calib_base_ft, &calib_ft) < 0) { pr_err("soctherm: ERROR: Improper CP or FT calib fuse.\n"); return -EINVAL; } nominal_calib_cp = 25; if (IS_T11X) nominal_calib_ft = 90; else if (IS_T14X || IS_T12X || IS_T13X) nominal_calib_ft = 105; else BUG(); /* use HI precision to calculate: use fuse_temp in 0.5C */ actual_temp_cp = 2 * nominal_calib_cp + calib_cp; actual_temp_ft = 2 * nominal_calib_ft + calib_ft; return 0; } static struct soctherm_fuse_correction_war no_fuse_war[] = { [TSENSE_CPU0] = { 1000000, 0 }, [TSENSE_CPU1] = { 1000000, 0 }, [TSENSE_CPU2] = { 1000000, 0 }, [TSENSE_CPU3] = { 1000000, 0 }, [TSENSE_MEM0] = { 1000000, 0 }, [TSENSE_MEM1] = { 1000000, 0 }, [TSENSE_GPU] = { 1000000, 0 }, [TSENSE_PLLX] = { 1000000, 0 }, }; static struct soctherm_fuse_correction_war t11x_fuse_war[] = { [TSENSE_CPU0] = { 1196400, -13600000 }, [TSENSE_CPU1] = { 1196400, -13600000 }, [TSENSE_CPU2] = { 1196400, -13600000 }, [TSENSE_CPU3] = { 1196400, -13600000 }, [TSENSE_MEM0] = { 1000000, -1000000 }, [TSENSE_MEM1] = { 1000000, -1000000 }, [TSENSE_GPU] = { 1124500, -9793100 }, [TSENSE_PLLX] = { 1224200, -14665000 }, }; static struct soctherm_fuse_correction_war t14x_fuse_war[] = { [TSENSE_CPU0] = { 1149000, -16753000 }, [TSENSE_CPU1] = { 1148800, -16287000 }, [TSENSE_CPU2] = { 1139100, -12552000 }, [TSENSE_CPU3] = { 1141800, -11061000 }, [TSENSE_MEM0] = { 1082300, -11061000 }, [TSENSE_MEM1] = { 1061800, -7596500 }, [TSENSE_GPU] = { 1078900, -10480000 }, [TSENSE_PLLX] = { 1125900, -14736000 }, }; /* old CP/FT */ static struct soctherm_fuse_correction_war t12x_fuse_war1[] = { [TSENSE_CPU0] = { 1148300, -6572300 }, [TSENSE_CPU1] = { 1126100, -5794600 }, [TSENSE_CPU2] = { 1155800, -7462800 }, [TSENSE_CPU3] = { 1134900, -6810800 }, [TSENSE_MEM0] = { 1062700, -4463200 }, [TSENSE_MEM1] = { 1084700, -5603400 }, [TSENSE_GPU] = { 1084300, -5111900 }, [TSENSE_PLLX] = { 1134500, -7410700 }, }; /* new CP1/CP2 */ static struct soctherm_fuse_correction_war t12x_fuse_war2[] = { [TSENSE_CPU0] = { 1135400, -6266900 }, [TSENSE_CPU1] = { 1122220, -5700700 }, [TSENSE_CPU2] = { 1127000, -6768200 }, [TSENSE_CPU3] = { 1110900, -6232000 }, [TSENSE_MEM0] = { 1122300, -5936400 }, [TSENSE_MEM1] = { 1145700, -7124600 }, [TSENSE_GPU] = { 1120100, -6000500 }, [TSENSE_PLLX] = { 1106500, -6729300 }, }; /* old ATE pattern */ static struct soctherm_fuse_correction_war t13x_fuse_war1[] = { [TSENSE_CPU0] = { 1119800, -6330400 }, [TSENSE_CPU1] = { 1094100, -3751800 }, [TSENSE_CPU2] = { 1108800, -3835200 }, [TSENSE_CPU3] = { 1103200, -5132100 }, [TSENSE_MEM0] = { 1168400, -11266000 }, [TSENSE_MEM1] = { 1185600, -10861000 }, [TSENSE_GPU] = { 1158500, -10714000 }, [TSENSE_PLLX] = { 1150000, -11899000 }, }; /* new ATE pattern */ static struct soctherm_fuse_correction_war t13x_fuse_war2[] = { [TSENSE_CPU0] = { 1126600, -9433500 }, [TSENSE_CPU1] = { 1110800, -7383000 }, [TSENSE_CPU2] = { 1113800, -6215200 }, [TSENSE_CPU3] = { 1129600, -8196100 }, [TSENSE_MEM0] = { 1132900, -6755300 }, [TSENSE_MEM1] = { 1142300, -7374200 }, [TSENSE_GPU] = { 1125100, -6350400 }, [TSENSE_PLLX] = { 1118100, -8208800 }, }; /** * soctherm_fuse_read_tsensor() - calculates therm_a and therm_b for a sensor * @sensor: The sensor for which to calculate. * * Reads the calibration data from the thermal sensor's fuses and then uses * that data to calculate the slope of the pulse/temperature * relationship, therm_a, and its x-intercept, therm_b. After correcting the * values based on their chip ID and whether precision is high or low, it * stores them in the sensor's registers so that the hardware can convert the * raw TSOSC reading into temperature in celsius. * * Return: 0 if successful, otherwise %-EINVAL */ 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; int fuse_rev; struct soctherm_fuse_correction_war *war; fuse_rev = tegra_fuse_calib_base_get_cp(NULL, NULL); if (fuse_rev < 0) return fuse_rev; tegra_fuse_get_tsensor_calib(sensor2tsensorcalib[sensor], &value); /* 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; /* first calculate therm_a and therm_b in Hi precision */ 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); /* FUSE correction WARs */ if (IS_T11X) war = PRECISION_IS_LOWER() ? &t11x_fuse_war[sensor] : &no_fuse_war[sensor]; else if (IS_T14X) war = PRECISION_IS_LOWER() ? &t14x_fuse_war[sensor] : &no_fuse_war[sensor]; else if (IS_T12X) war = fuse_rev ? &t12x_fuse_war1[sensor] : &t12x_fuse_war2[sensor]; else if (IS_T13X) war = fuse_rev == 2 ? &t13x_fuse_war1[sensor] : &t13x_fuse_war2[sensor]; else war = &no_fuse_war[sensor]; therm_a = div64_s64_precise((s64)therm_a * war->a, (s64)1000000LL); therm_b = div64_s64_precise((s64)therm_b * war->a + war->b, (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; } /** * soctherm_therm_trip_init() - configure PMC's thermal-shutdown behavior * @data: Power management unit thermal sensor initialization data * * Takes a given set of data and writes it to SCRATCH54 and SCRATCH55, which * PMC will use if SOC_THERM requests a shutdown based on excessive * temperature (i.e. a thermtrip). */ static void soctherm_therm_trip_init(struct tegra_thermtrip_pmic_data *data) { if (!data) return; tegra_pmc_enable_thermal_trip(); tegra_pmc_config_thermal_trip(data); } /** * soctherm_adjust_cpu_zone() - Adjusts the soctherm CPU zone * @therm: soctherm_therm_id specifying the sensor group to adjust * * Changes SOC_THERM registers based on the CPU and PLLX temperatures. * Programs hotspot offsets per CPU or GPU and PLLX difference of temperature, * stops or starts CPUn TSOSCs, and programs hotspot offsets per configuration. * This function is called in soctherm_init_platform_data(), * tegra_soctherm_adjust_cpu_zone() and tegra_soctherm_adjust_core_zone(). */ static void soctherm_adjust_zone(int tz) { u32 r, s; int i; long ztemp, pll_temp, diff; bool low_voltage; if (soctherm_suspended) return; if (tz == THERM_CPU) low_voltage = vdd_cpu_low_voltage; else if (tz == THERM_GPU) low_voltage = vdd_core_low_voltage; else if (tz == THERM_MEM) low_voltage = vdd_core_low_voltage; else return; if (low_voltage) { r = soctherm_readl(TS_TEMP1); s = soctherm_readl(TS_TEMP2); /* get pllx temp */ pll_temp = temp_translate(REG_GET(s, TS_TEMP2_PLLX_TEMP)); ztemp = pll_temp; /* initialized */ /* get therm-zone temp */ if (tz == THERM_CPU) ztemp = temp_translate(REG_GET(r, TS_TEMP1_CPU_TEMP)); else if (tz == THERM_GPU) ztemp = temp_translate(REG_GET(r, TS_TEMP1_GPU_TEMP)); else if (tz == THERM_MEM) ztemp = temp_translate(REG_GET(s, TS_TEMP2_MEM_TEMP)); if (ztemp > pll_temp) diff = ztemp - pll_temp; else diff = 0; /* cap hotspot offset to max offset from pdata */ if (diff > plat_data.therm[tz].hotspot_offset) diff = plat_data.therm[tz].hotspot_offset; /* Program hotspot offsets per ~ PLL diff */ r = soctherm_readl(TS_HOTSPOT_OFF); if (tz == THERM_CPU) r = REG_SET(r, TS_HOTSPOT_OFF_CPU, diff / 1000); else if (tz == THERM_GPU) r = REG_SET(r, TS_HOTSPOT_OFF_GPU, diff / 1000); else if (tz == THERM_MEM) r = REG_SET(r, TS_HOTSPOT_OFF_MEM, diff / 1000); soctherm_writel(r, TS_HOTSPOT_OFF); /* Stop all TSENSE's mapped to */ for (i = 0; i < TSENSE_SIZE; i++) { if (tsensor2therm_map[i] != tz) continue; r = soctherm_readl(TS_TSENSE_REG_OFFSET (TS_CPU0_CONFIG0, i)); r = REG_SET(r, TS_CPU0_CONFIG0_STOP, 1); soctherm_writel(r, TS_TSENSE_REG_OFFSET (TS_CPU0_CONFIG0, i)); } } else { /* UN-Stop all TSENSE's mapped to */ for (i = 0; i < TSENSE_SIZE; i++) { if (tsensor2therm_map[i] != tz) continue; r = soctherm_readl(TS_TSENSE_REG_OFFSET (TS_CPU0_CONFIG0, i)); r = REG_SET(r, TS_CPU0_CONFIG0_STOP, 0); soctherm_writel(r, TS_TSENSE_REG_OFFSET (TS_CPU0_CONFIG0, i)); } /* default to configured offset for */ diff = plat_data.therm[tz].hotspot_offset; /* Program hotspot offsets per config */ r = soctherm_readl(TS_HOTSPOT_OFF); if (tz == THERM_CPU) r = REG_SET(r, TS_HOTSPOT_OFF_CPU, diff / 1000); else if (tz == THERM_GPU) r = REG_SET(r, TS_HOTSPOT_OFF_GPU, diff / 1000); else if (tz == THERM_MEM) r = REG_SET(r, TS_HOTSPOT_OFF_MEM, diff / 1000); soctherm_writel(r, TS_HOTSPOT_OFF); } } /** * soctherm_init_platform_data() - Initializes the platform data. * * Cleans up some platform data in preparation for configuring the * hardware and configures the hardware as specified by the cleaned up * platform data. * * Initializes unset parameters for CPU, GPU, MEM, and PLL * based on the default values for the sensors on the Tegra chip in use. * * Sets the temperature sensor PDIV (post divider) register which * contains the temperature sensor PDIV for the CPU, GPU, MEM, and PLLX * * Sets the configurations for each of the sensors. * * Sanitizes thermal trips for each thermal zone. * * Writes hotspot offsets to TS_HOTSPOT_OFF register. * * Checks the throttling priorities and makes sure that they are * in the correct order for throttle types THROTTLE_OC1 though THROTTLE_OC4. * * Initializes PSKIP parameters. These parameters are used during a thermal * trip to calculate the amount of throttling of the CPU or GPU for each * thermal trip type (i.e. THROTTLE_LIGHT or THROTTLE_HEAVY) * * Initializes the throttling thresholds for the CPU, GPU, MEM, and PLL * * Checks if the priorities of heavy and light throttling are in * the correct order. * * Initializes the STATS_CTL and OC_STATS_CTL registers for stat collection * * Enables PMC shutdown based on the platform data * * Programs the temperatures at which hardware shutdowns occur. * * soctherm_init_platform_data ensures that the system will function as * expected when it resumes from suspended state or on initial start up. * * Return: 0 on success. -EINVAL is returned otherwise */ static int soctherm_init_platform_data(void) { struct soctherm_therm *therm; struct soctherm_sensor *s; struct soctherm_sensor sensor_defaults; int i, j, k; long rem; long gsh = MAX_HIGH_TEMP; u32 r; if (IS_T11X) sensor_defaults = default_t11x_sensor_params; else if (IS_T14X) sensor_defaults = default_t14x_sensor_params; else if ((IS_T12X || IS_T13X)) sensor_defaults = default_t12x_sensor_params; else BUG(); /* 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]; if (!(s->sensor_enable)) s->sensor_enable = therm->zone_enable; s->tall = s->tall ?: sensor_defaults.tall; s->tiddq = s->tiddq ?: sensor_defaults.tiddq; s->ten_count = s->ten_count ?: sensor_defaults.ten_count; s->tsample = s->tsample ?: sensor_defaults.tsample; s->tsamp_ate = s->tsamp_ate ?: sensor_defaults.tsamp_ate; s->pdiv = s->pdiv ?: sensor_defaults.pdiv; s->pdiv_ate = s->pdiv_ate ?: sensor_defaults.pdiv_ate; } /* 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 */ if (soctherm_fuse_read_calib_base() < 0) return -EINVAL; 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; } } soctherm_adjust_zone(THERM_CPU); soctherm_adjust_zone(THERM_GPU); soctherm_adjust_zone(THERM_MEM); /* 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) { pr_warn( "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); /* Thermal HW throttle programming */ for (i = 0; i < THROTTLE_SIZE; i++) { /* Sanitize HW throttle priority for OC1 - OC4 (not OC5) */ if ((i != THROTTLE_OC5) && (!plat_data.throttle[i].priority)) plat_data.throttle[i].priority = 0xE + i; /* Setup PSKIP parameters */ soctherm_throttle_program(i); /* Setup throttle thresholds per THERM */ for (j = 0; j < THERM_SIZE; j++) { if ((therm2dev[j] == THROTTLE_DEV_NONE) || (!plat_data.throttle[i].devs[therm2dev[j]].enable)) 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 && i <= THROTTLE_HEAVY) prog_hw_threshold(&therm->trips[k], j, i); } } r = REG_SET(0, THROT_GLOBAL_ENB, 1); if (IS_T13X) clk_reset13_writel(r, THROT13_GLOBAL_CFG); else soctherm_writel(r, THROT_GLOBAL_CFG); if (plat_data.throttle[THROTTLE_HEAVY].priority < plat_data.throttle[THROTTLE_LIGHT].priority) pr_err("soctherm: ERROR: Priority of HEAVY less than LIGHT\n"); /* 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); soctherm_writel(OC_STATS_CTL_EN_ALL, OC_STATS_CTL); /* Enable PMC to shutdown */ soctherm_therm_trip_init(plat_data.tshut_pmu_trip_data); r = clk_reset_readl(CAR_SUPER_CLK_DIVIDER_REGISTER()); r = REG_SET(r, CDIVG_USE_THERM_CONTROLS, 1); clk_reset_writel(r, CAR_SUPER_CLK_DIVIDER_REGISTER()); /* 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) continue; if (i == THERM_GPU) { gsh = therm->trips[j].trip_temp; } else if ((i == THERM_MEM) && (gsh != MAX_HIGH_TEMP) && (therm->trips[j].trip_temp != gsh)) { pr_warn("soctherm: Force TRIP temp: MEM = GPU"); therm->trips[j].trip_temp = gsh; } prog_hw_shutdown(&therm->trips[j], i); } } return 0; } /** * soctherm_suspend_locked() - suspends SOC_THERM IP block * * Note: This function should never be directly called because * it's not thread-safe. Instead, soctherm_suspend() should be called. * Performs SOC_THERM suspension. It will disable SOC_THERM device interrupts. * SOC_THERM will need to be reinitialized. * */ static void soctherm_suspend_locked(void) { if (!soctherm_suspended) { soctherm_writel((u32)-1, TH_INTR_DISABLE); soctherm_writel((u32)-1, OC_INTR_DISABLE); disable_irq(INT_THERMAL); disable_irq(INT_EDP); soctherm_init_platform_done = false; soctherm_suspended = true; /* soctherm_clk_enable(false);*/ } } /** * soctherm_suspend() - Suspends the SOC_THERM device * * Suspends SOC_THERM and prevents interrupts from occurring * and SOC_THERM from interrupting the CPU. * * Return: 0 on success. */ static int soctherm_suspend(void) { mutex_lock(&soctherm_suspend_resume_lock); soctherm_suspend_locked(); mutex_unlock(&soctherm_suspend_resume_lock); return 0; } /** * soctherm_resume_locked() - Resumes soctherm if it is suspended * * Enables device interrupt generation for thermal and EDP when soctherm * platform initialization is done. */ static void soctherm_resume_locked(void) { if (soctherm_suspended) { /* soctherm_clk_enable(true);*/ soctherm_suspended = false; soctherm_init_platform_data(); soctherm_init_platform_done = true; soctherm_update(); enable_irq(INT_THERMAL); enable_irq(INT_EDP); } } /** * soctherm_resume() - wrapper for soctherm_resume_locked() * * Grabs the soctherm_suspend_resume_lock and then resumes SOC_THERM by running * soctherm_resume_locked(). * * Return: 0 */ static int soctherm_resume(void) { mutex_lock(&soctherm_suspend_resume_lock); soctherm_resume_locked(); mutex_unlock(&soctherm_suspend_resume_lock); return 0; } /** * soctherm_sync() - Syncs soctherm * * If soctherm is suspended, reinitializes the SOC_THERM IP block registers * from the platform data and updates each zone. Otherwise only the * latter occurs. */ static int soctherm_sync(void) { mutex_lock(&soctherm_suspend_resume_lock); if (soctherm_suspended) { soctherm_resume_locked(); soctherm_suspend_locked(); } else { soctherm_update(); } mutex_unlock(&soctherm_suspend_resume_lock); return 0; } late_initcall_sync(soctherm_sync); /** * soctherm_pm_suspend() - reacts to system PM suspend event * @nb: pointer to notifier_block. Currently not being used * @event: type of action (suspend/resume) * @data: argument for callback, currently not being used * * Currently supports %PM_SUSPEND_PREPARE. Ignores %PM_POST_SUSPEND * * Return: %NOTIFY_OK */ static int soctherm_pm_suspend(struct notifier_block *nb, unsigned long event, void *data) { if (event == PM_SUSPEND_PREPARE) { soctherm_suspend(); pr_info("tegra_soctherm: suspended\n"); } return NOTIFY_OK; } /** * soctherm_pm_resume() - reacts to system PM resume event * @nb: pointer to notifier_block. Currently not being used * @event: type of action (suspend/resume) * @data: argument for callback, currently not being used * * Currently supports %PM_POST_SUSPEND. Ignores %PM_SUSPEND_PREPARE * * Return: %NOTIFY_OK */ static int soctherm_pm_resume(struct notifier_block *nb, unsigned long event, void *data) { if (event == PM_POST_SUSPEND) { soctherm_resume(); pr_info("tegra_soctherm: resumed\n"); } return NOTIFY_OK; } static struct notifier_block soctherm_suspend_nb = { .notifier_call = soctherm_pm_suspend, .priority = -2, }; static struct notifier_block soctherm_resume_nb = { .notifier_call = soctherm_pm_resume, .priority = 2, }; /** * soctherm_oc_irq_lock() - locks the over-current interrupt request * @data: Interrupt request data * * Looks up the chip data from @data and locks the mutex associated with * a particular over-current interrupt request. */ static void soctherm_oc_irq_lock(struct irq_data *data) { struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data); mutex_lock(&d->irq_lock); } /** * soctherm_oc_irq_sync_unlock() - Unlocks the OC interrupt request * @data: Interrupt request data * * Looks up the interrupt request data @data and unlocks the mutex associated * with a particular over-current interrupt request. */ static void soctherm_oc_irq_sync_unlock(struct irq_data *data) { struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data); mutex_unlock(&d->irq_lock); } /** * soctherm_oc_irq_enable() - Enables the SOC_THERM over-current interrupt queue * @data: irq_data structure of the chip * * Sets the irq_enable bit of SOC_THERM allowing SOC_THERM * to respond to over-current interrupts. * */ static void soctherm_oc_irq_enable(struct irq_data *data) { struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data); d->irq_enable |= BIT(data->hwirq); } /** * soctherm_oc_irq_disable() - Disables overcurrent interrupt requests * @irq_data: The interrupt request information * * Clears the interrupt request enable bit of the overcurrent * interrupt request chip data. * * Return: Nothing is returned (void) */ static void soctherm_oc_irq_disable(struct irq_data *data) { struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data); d->irq_enable &= ~BIT(data->hwirq); } static int soctherm_oc_irq_set_type(struct irq_data *data, unsigned int type) { return 0; } /** * soctherm_oc_irq_set_wake() - Set the overcurrent interrupt request * to "wake" * @irq_data: Interrupt request information * @on: Whether to enable or disable power management wakeup * * Configure the GPIO associated with a SOC_THERM over-current * interrupt to wake the system from sleep * * It may be necessary to wake the system from sleep mode so that * SOC_THERM can provide proper over-current throttling. * * Return: 0 on success, -EINVAL if there is no wakeup support * for that given hardware irq, or the gpio number if there is * no gpio_to_irq for that gpio. */ static int soctherm_oc_irq_set_wake(struct irq_data *data, unsigned int on) { int gpio; int gpio_irq; gpio = soctherm_ocx_to_wake_gpio[data->hwirq]; if (!gpio_is_valid(gpio)) { pr_err("No wakeup supported for irq %lu\n", data->hwirq); return -EINVAL; } gpio_irq = gpio_to_irq(gpio); if (gpio_irq < 0) { pr_err("No gpio_to_irq for gpio %d\n", gpio); return gpio; } irq_set_irq_wake(gpio_irq, on); return 0; } /** * soctherm_oc_irq_map() - SOC_THERM interrupt request domain mapper * @h: Interrupt request domain * @virq: Virtual interrupt request number * @hw: Hardware interrupt request number * * Mapping callback function for SOC_THERM's irq_domain. When a SOC_THERM * interrupt request is called, the irq_domain takes the request's virtual * request number (much like a virtual memory address) and maps it to a * physical hardware request number. * * When a mapping doesn't already exist for a virtual request number, the * irq_domain calls this function to associate the virtual request number with * a hardware request number. * * Return: 0 */ static int soctherm_oc_irq_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) { struct soctherm_oc_irq_chip_data *data = h->host_data; irq_set_chip_data(virq, data); irq_set_chip(virq, &data->irq_chip); irq_set_nested_thread(virq, 1); set_irq_flags(virq, IRQF_VALID); return 0; } static struct irq_domain_ops soctherm_oc_domain_ops = { .map = soctherm_oc_irq_map, .xlate = irq_domain_xlate_twocell, }; /** * tegra11_soctherem_oc_int_init() - Initial enabling of the over * current interrupts * @irq_base: The interrupt request base number from platform data * @num_irqs: The number of new interrupt requests * * Sets the over current interrupt request chip data * * Return: 0 on success or if overcurrent interrupts are not enabled, * -ENOMEM (out of memory), or irq_base if the function failed to * allocate the irqs */ static int tegra11_soctherem_oc_int_init(int irq_base, int num_irqs) { if (irq_base <= 0 || !num_irqs) { pr_info("%s(): OC interrupts are not enabled\n", __func__); return 0; } mutex_init(&soc_irq_cdata.irq_lock); soc_irq_cdata.irq_enable = 0; soc_irq_cdata.irq_chip.name = "soc_therm_oc"; soc_irq_cdata.irq_chip.irq_bus_lock = soctherm_oc_irq_lock, soc_irq_cdata.irq_chip.irq_bus_sync_unlock = soctherm_oc_irq_sync_unlock, soc_irq_cdata.irq_chip.irq_disable = soctherm_oc_irq_disable, soc_irq_cdata.irq_chip.irq_enable = soctherm_oc_irq_enable, soc_irq_cdata.irq_chip.irq_set_type = soctherm_oc_irq_set_type, soc_irq_cdata.irq_chip.irq_set_wake = soctherm_oc_irq_set_wake, irq_base = irq_alloc_descs(irq_base, 0, num_irqs, 0); if (irq_base < 0) { pr_err("%s: Failed to allocate IRQs: %d\n", __func__, irq_base); return irq_base; } soc_irq_cdata.domain = irq_domain_add_legacy(NULL, num_irqs, irq_base, 0, &soctherm_oc_domain_ops, &soc_irq_cdata); if (!soc_irq_cdata.domain) { pr_err("%s: Failed to create IRQ domain\n", __func__); return -ENOMEM; } pr_info("%s(): OC interrupts enabled successful\n", __func__); return 0; } static int core_rail_regulator_notifier_cb( struct notifier_block *nb, unsigned long event, void *v) { int uv = (int)((long)v); int rv = NOTIFY_DONE; int core_vmin_limit_uv; if (IS_T12X) { core_vmin_limit_uv = 900000; if (event & REGULATOR_EVENT_OUT_POSTCHANGE) { if (uv >= core_vmin_limit_uv) { tegra_soctherm_adjust_core_zone(true); rv = NOTIFY_OK; } } else if (event & REGULATOR_EVENT_OUT_PRECHANGE) { if (uv < core_vmin_limit_uv) { tegra_soctherm_adjust_core_zone(false); rv = NOTIFY_OK; } } } return rv; } static int __init soctherm_core_rail_notify_init(void) { int ret; static struct notifier_block vmin_condition_nb; vmin_condition_nb.notifier_call = core_rail_regulator_notifier_cb; ret = tegra_dvfs_rail_register_notifier(tegra_core_rail, &vmin_condition_nb); if (ret) { pr_err("%s: Failed to register core rail notifier\n", __func__); return ret; } return 0; } late_initcall_sync(soctherm_core_rail_notify_init); /** * tegra11_soctherm_init() - initializes SOC_THERM IP Block * @data: pointer to board-specific information * * Initialize and enable SOC_THERM clocks, sanitize platform data, configure * SOC_THERM according to platform data, and set up interrupt handling for * OC events. * * Return: -1 if initialization failed, 0 otherwise */ int __init tegra11_soctherm_init(struct soctherm_platform_data *data) { int ret; tegra_chip_id = tegra_get_chip_id(); if (!(IS_T11X || IS_T14X || IS_T12X || IS_T13X)) { pr_err("%s: Unknown chip_id %d", __func__, tegra_chip_id); return -1; } register_pm_notifier(&soctherm_suspend_nb); register_pm_notifier(&soctherm_resume_nb); if (!data) return -1; plat_data = *data; if (soctherm_clk_init() < 0) return -1; if (soctherm_clk_enable(true) < 0) return -1; if (soctherm_init_platform_data() < 0) return -1; soctherm_init_platform_done = true; ret = tegra11_soctherem_oc_int_init(data->oc_irq_base, data->num_oc_irqs); if (ret < 0) { pr_err("soctherem_oc_int_init failed: %d\n", ret); return ret; } if (request_threaded_irq(INT_THERMAL, soctherm_thermal_isr, soctherm_thermal_thread_func, IRQF_ONESHOT, "soctherm_thermal", NULL) < 0) return -1; if (request_threaded_irq(INT_EDP, soctherm_edp_isr, soctherm_edp_thread_func, IRQF_ONESHOT, "soctherm_edp", NULL) < 0) return -1; return 0; } /** * tegra_soctherm_adjust_cpu_zone() - Adjusts the CPU zone of Tegra soctherm * @high_voltage_range: Flag indicating whether or not the system is * within the highest voltage range * * If a particular VDD_CPU voltage threshold has been crossed (either up or * down), invokes soctherm_adjust_cpu_zone(). * This function should be called by code outside this file when VDD_CPU crosses * a particular threshold. */ void tegra_soctherm_adjust_cpu_zone(bool high_voltage_range) { if (!vdd_cpu_low_voltage != high_voltage_range) { vdd_cpu_low_voltage = !high_voltage_range; soctherm_adjust_zone(THERM_CPU); } } void tegra_soctherm_adjust_core_zone(bool high_voltage_range) { if ((IS_T12X || IS_T13X)) { if (!vdd_core_low_voltage != high_voltage_range) { vdd_core_low_voltage = !high_voltage_range; soctherm_adjust_zone(THERM_GPU); soctherm_adjust_zone(THERM_MEM); } } } #ifdef CONFIG_DEBUG_FS /** * regs_show() - show callback for regs debugfs * @s: seq_file for registers values to be written to * @data: a void pointer for callback, currently not being used * * Gathers various register values and system status, then * formats and display the information as a debugfs virtual file. * This function allows easy access to debugging information. * * Return: -1 if fail, 0 otherwise */ static int regs_show(struct seq_file *s, void *data) { u32 r; u32 state; int tcpu[TSENSE_SIZE]; int i, j, level; uint m, n, q; char *depth; seq_printf(s, "-----TSENSE (precision %s fuse %d convert %s)-----\n", PRECISION_TO_STR(), tegra_fuse_calib_base_get_cp(NULL, NULL), read_hw_temp ? "HW" : "SW"); if (soctherm_suspended) { seq_puts(s, "SOC_THERM is SUSPENDED\n"); return 0; } 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_STOP); seq_printf(s, "Stop(%d) ", state); 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++) { 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, "CPU Throt"); seq_printf(s, "(%s) ", state ? state == CTL_LVL0_CPU0_CPU_THROT_LIGHT ? "L" : state == CTL_LVL0_CPU0_CPU_THROT_HEAVY ? "H" : "H+L" : "none"); state = REG_GET(r, CTL_LVL0_CPU0_GPU_THROT); seq_puts(s, "GPU Throt"); seq_printf(s, "(%s) ", state ? state == CTL_LVL0_CPU0_GPU_THROT_LIGHT ? "L" : state == CTL_LVL0_CPU0_GPU_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_MEM_EN); seq_printf(s, " MEM 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%08x\n", r); seq_puts(s, "---------------------------------------------------\n"); 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); if (IS_T13X) { state = REG_GET(r, XPU_PSKIP_STATUS_ENABLED); seq_printf(s, "%s PSKIP STATUS: ", throt_dev_names[THROTTLE_DEV_CPU]); seq_printf(s, "enabled(%d)\n", state); } else { state = REG_GET(r, XPU_PSKIP_STATUS_M); seq_printf(s, "%s PSKIP STATUS: M(%d) ", throt_dev_names[THROTTLE_DEV_CPU], state); state = REG_GET(r, XPU_PSKIP_STATUS_N); seq_printf(s, "N(%d) ", state); state = REG_GET(r, XPU_PSKIP_STATUS_ENABLED); seq_printf(s, "enabled(%d)\n", state); } r = soctherm_readl(GPU_PSKIP_STATUS); if ((IS_T12X || IS_T13X)) { state = REG_GET(r, XPU_PSKIP_STATUS_ENABLED); seq_printf(s, "%s PSKIP STATUS: ", throt_dev_names[THROTTLE_DEV_GPU]); seq_printf(s, "enabled(%d)\n", state); } else { state = REG_GET(r, XPU_PSKIP_STATUS_M); seq_printf(s, "%s PSKIP STATUS: M(%d) ", throt_dev_names[THROTTLE_DEV_GPU], state); state = REG_GET(r, XPU_PSKIP_STATUS_N); seq_printf(s, "N(%d) ", state); state = REG_GET(r, XPU_PSKIP_STATUS_ENABLED); seq_printf(s, "enabled(%d)\n", state); } seq_puts(s, "---------------------------------------------------\n"); seq_puts(s, "THROTTLE control and PSKIP configuration:\n"); seq_printf(s, "%5s %3s %2s %7s %8s %7s %8s %4s %4s %5s ", "throt", "dev", "en", " depth ", "dividend", "divisor", "duration", "step", "prio", "delay"); seq_printf(s, "%2s %2s %2s %2s %2s %2s ", "LL", "HW", "PG", "MD", "01", "EN"); seq_printf(s, "%8s %8s %8s %8s %8s\n", "thresh", "period", "count", "filter", "stats"); /* display throttle_cfg's of all alarms including OC5 */ for (i = 0; i < THROTTLE_SIZE; i++) { for (j = 0; j < THROTTLE_DEV_SIZE; j++) { r = soctherm_readl(THROT_PSKIP_CTRL(i, j)); state = REG_GET(r, THROT_PSKIP_CTRL_ENABLE); seq_printf(s, "%5s %3s %2d ", j ? "" : throt_names[i], throt_dev_names[j], state); if (!state) { seq_puts(s, "\n"); continue; } level = THROT_LEVEL_NONE; /* invalid */ depth = ""; q = 0; if (IS_T13X && j == THROTTLE_DEV_CPU) { state = REG_GET(r, THROT_PSKIP_CTRL_VECT_CPU); if (state == THROT_VECT_HVY) { level = THROT_LEVEL_HVY; depth = "hi"; } else if (state == THROT_VECT_MED) { level = THROT_LEVEL_MED; depth = "med"; } else if (state == THROT_VECT_LOW) { level = THROT_LEVEL_LOW; depth = "low"; } } if ((IS_T12X || IS_T13X) && j == THROTTLE_DEV_GPU) { state = REG_GET(r, THROT_PSKIP_CTRL_VECT_GPU); /* Mapping is hard-coded in gk20a:nv_therm */ if (state == THROT_VECT_HVY) { q = 87; depth = "hi"; } else if (state == THROT_VECT_MED) { q = 75; depth = "med"; } else if (state == THROT_VECT_LOW) { q = 50; depth = "low"; } } if (level == THROT_LEVEL_NONE) r = 0; else if (IS_T13X && j == THROTTLE_DEV_CPU) r = clk_reset13_readl( THROT13_PSKIP_CTRL_CPU(level)); else r = soctherm_readl(THROT_PSKIP_CTRL(i, j)); m = REG_GET(r, THROT_PSKIP_CTRL_DIVIDEND); n = REG_GET(r, THROT_PSKIP_CTRL_DIVISOR); q = q ?: 100 - (((100 * (m+1)) + ((n+1) / 2)) / (n+1)); seq_printf(s, "%2u%% %3s ", q, depth); seq_printf(s, "%8u ", m); seq_printf(s, "%7u ", n); if (IS_T13X && j == THROTTLE_DEV_CPU) r = clk_reset13_readl( THROT13_PSKIP_RAMP_CPU(level)); else r = soctherm_readl(THROT_PSKIP_RAMP(i, j)); state = REG_GET(r, THROT_PSKIP_RAMP_DURATION); seq_printf(s, "%8d ", state); state = REG_GET(r, THROT_PSKIP_RAMP_STEP); seq_printf(s, "%4d ", state); r = soctherm_readl(THROT_PRIORITY_CTRL(i)); state = REG_GET(r, THROT_PRIORITY_LITE_PRIO); seq_printf(s, "%4d ", state); r = soctherm_readl(THROT_DELAY_CTRL(i)); state = REG_GET(r, THROT_DELAY_LITE_DELAY); seq_printf(s, "%5d ", state); if (i >= THROTTLE_OC1) { r = soctherm_readl(ALARM_CFG(i)); state = REG_GET(r, OC1_CFG_LONG_LATENCY); seq_printf(s, "%2d ", state); state = REG_GET(r, OC1_CFG_HW_RESTORE); seq_printf(s, "%2d ", state); state = REG_GET(r, OC1_CFG_PWR_GOOD_MASK); seq_printf(s, "%2d ", state); state = REG_GET(r, OC1_CFG_THROTTLE_MODE); seq_printf(s, "%2d ", state); state = REG_GET(r, OC1_CFG_ALARM_POLARITY); seq_printf(s, "%2d ", state); state = REG_GET(r, OC1_CFG_EN_THROTTLE); seq_printf(s, "%2d ", state); r = soctherm_readl(ALARM_CNT_THRESHOLD(i)); seq_printf(s, "%8d ", r); r = soctherm_readl(ALARM_THROTTLE_PERIOD(i)); seq_printf(s, "%8d ", r); r = soctherm_readl(ALARM_ALARM_COUNT(i)); seq_printf(s, "%8d ", r); r = soctherm_readl(ALARM_FILTER(i)); seq_printf(s, "%8d ", r); r = soctherm_readl(ALARM_STATS(i)); seq_printf(s, "%8d ", r); } seq_puts(s, "\n"); } } return 0; } /** * temp_log_show() - "show" callback for temp_log debugfs node * @s: pointer to the seq_file record to write the log through * @data: not used * * The temperature log contains the time (seconds.nanoseconds), and the * temperature of each of the thermal sensors, if there is a valid temperature * capture available. Makes sure that SOC_THERM is left in the same state in * which it was previously (suspended/resumed). * * Return: 0 */ static int temp_log_show(struct seq_file *s, void *data) { u32 r, state; int i; u64 ts; u_long ns; bool was_suspended = false; ts = cpu_clock(0); ns = do_div(ts, 1000000000); seq_printf(s, "%6lu.%06lu", (u_long) ts, ns / 1000); if (soctherm_suspended) { mutex_lock(&soctherm_suspend_resume_lock); soctherm_resume_locked(); was_suspended = true; } 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 (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"); if (was_suspended) { soctherm_suspend_locked(); mutex_unlock(&soctherm_suspend_resume_lock); } return 0; } /** * regs_open() - wraps single_open to associate internal regs_show() * @inode: inode related to the file * @file: pointer to a file to be manipulated with single_open * * Return: Passes along the return value from single_open(). */ 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, }; /** * convert_get() - indicates software or hardware temperature conversion * @data: argument for callback, currently not being used * @val: pointer to a u64 location to store flag value * * Stores boolean flag into memory address pointed to by val. * The flag indicates whether SOC_THERM is using * software or hardware temperature conversion. * * Return: 0 */ static int convert_get(void *data, u64 *val) { *val = !read_hw_temp; return 0; } /** * convert_set() - Sets a flag indicating which temperature is * being read. * @data: Opaque pointer to data passed in from filesystem layer * @val: The flag value * * Sets the read_hw_temp file static flag. This flag indicates whether * the hardware temperature or the software temperature is being * read. * * Return: 0 on success. */ static int convert_set(void *data, u64 val) { read_hw_temp = !val; return 0; } /** * cputemp_get() - gets the CPU temperature. * @data: not used * @val: a pointer in which the temperature will be placed. * * Reads the temperature of the thermal sensor associated with the CPU. * * Return: 0 */ static int cputemp_get(void *data, u64 *val) { u32 reg; reg = soctherm_readl(TS_TEMP1); *val = temp_translate(REG_GET(reg, TS_TEMP1_CPU_TEMP)); return 0; } /** * cputemp_set() - Puts a particular value into the CPU temperature register * @data: The pointer to data. Currently not being used. * @temp: The temperature to be written to the register * * This function only works if temperature overrides have been enabled. * Clears the original register CPU temperature, converts the given * temperature to a register value, and writes it to the CPU temp register. * Used for debugfs. * * Return: 0 (success). */ static int cputemp_set(void *data, u64 temp) { u32 reg_val = temp_translate_rev(temp); u32 reg_orig = soctherm_readl(TS_TEMP1); reg_val = (reg_val << 16) | (reg_orig & 0xffff); soctherm_writel(reg_val, TS_TEMP1); return 0; } /** * gputemp_get() - retrieve GPU temperature from its register * @data: argument for callback, currently not being used * @val: pointer to a u64 location to store GPU temperature value * * Reads register value associated with the temperature sensor for the GPU * and stores it in the memory address pointed by val. * * Return: 0 */ static int gputemp_get(void *data, u64 *val) { u32 reg; reg = soctherm_readl(TS_TEMP1); *val = temp_translate(REG_GET(reg, TS_TEMP1_GPU_TEMP)); return 0; } /** * gputemp_set() - Puts a particular value into the GPU temperature register * @data: The pointer to data. Currently not being used. * @temp: The temperature to be written to the register * * This function only works if temperature overrides have been enabled. * Clears the original GPU temperature register, converts the given * temperature to a register value, and writes it to the GPU temp register. * The @temp needs to be in the units of the SOC_THERM register temperature * bitfield. * Used for debugfs. * * Return: 0 (success). */ static int gputemp_set(void *data, u64 temp) { u32 reg_val = temp_translate_rev(temp); u32 reg_orig = soctherm_readl(TS_TEMP1); reg_val = reg_val | (reg_orig & 0xffff0000); soctherm_writel(reg_val, TS_TEMP1); return 0; } /** * memtemp_get() - gets the memory temperature. * @data: not used * @val: a pointer in which the temperature will be placed. * * Reads the temperature of the thermal sensor associated with the memory. * * Return: 0 */ static int memtemp_get(void *data, u64 *val) { u32 reg; reg = soctherm_readl(TS_TEMP2); *val = temp_translate(REG_GET(reg, TS_TEMP2_MEM_TEMP)); return 0; } /** * memtemp_set() - Overrides the memory temperature * in hardware * @data: Opaque pointer to data; not used * @temp: The temperature to be written to the register * * Clears the memory temperature register, converts @temp to * a register value, and writes the converted value to the register * * Function only works when temperature overrides are enabled. * * This function is called to debug/test temperature * trip points regarding MEM temperatures * * Return: 0 on success. */ static int memtemp_set(void *data, u64 temp) { u32 reg_val = temp_translate_rev(temp); u32 reg_orig = soctherm_readl(TS_TEMP2); reg_val = (reg_val << 16) | (reg_orig & 0xffff); soctherm_writel(reg_val, TS_TEMP2); return 0; } /** * plltemp_get() - Gets the phase-locked loop temperature. * @data: Opaque pointer to data * @val: The pll temperature * * The temperature value is read in from the register. * The variable pointed to by @val is set to this temperature value. * * This function is used in debugfs * * Return: 0 on success. */ static int plltemp_get(void *data, u64 *val) { u32 reg; reg = soctherm_readl(TS_TEMP2); *val = temp_translate(REG_GET(reg, TS_TEMP2_PLLX_TEMP)); return 0; } /** * plltemp_set() - Stores a particular value into the PLLX temperature register * @data: The pointer to data. Currently not being used. * @temp: The temperature to be written to the register * * This function only works if temperature overrides have been enabled. * Clears the original PLLX temperature register, converts the given * temperature to a register value, and writes it to the PLLX temp register. * Used for debugfs. * * Return: 0 (success). */ static int plltemp_set(void *data, u64 temp) { u32 reg_val = temp_translate_rev(temp); u32 reg_orig = soctherm_readl(TS_TEMP2); reg_val = reg_val | (reg_orig & 0xffff0000); soctherm_writel(reg_val, TS_TEMP2); return 0; } /** * tempoverride_get() - gets the temperature sensor software override value * @data: not used * @val: a pointer in which the value will be placed. * * Gets whether software override of the temperature is enabled. If it is * then TSENSOR_TEMP1/TSENSOR_TEMP2 will be set by the tsense block. If not, * then software will have to set it. * * Return: 0 */ static int tempoverride_get(void *data, u64 *val) { *val = soctherm_readl(TS_TEMP_SW_OVERRIDE); return 0; } /** * tempoverride_set() - enables or disables software temperature override * @data: argument for callback, currently not being used * @val: val should be 1 to enable override. 0 to disable override * * For debugging purposes, this function allows or disallow software * to override temperature reading. This is useful when testing how SOC_THERM * reacts to different temperature. * * Return: 0 */ static int tempoverride_set(void *data, u64 val) { soctherm_writel(val, TS_TEMP_SW_OVERRIDE); return 0; } DEFINE_SIMPLE_ATTRIBUTE(convert_fops, convert_get, convert_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(cputemp_fops, cputemp_get, cputemp_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(gputemp_fops, gputemp_get, gputemp_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(memtemp_fops, memtemp_get, memtemp_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(plltemp_fops, plltemp_get, plltemp_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(tempoverride_fops, tempoverride_get, tempoverride_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, }; /** * soctherm_debug_init() - initializes the SOC_THERM debugfs files * * Creates a tegra_soctherm directory in debugfs, then creates all of the * debugfs files, setting the functions that are called when each respective * file is read or written. * * Return: 0 */ static int __init soctherm_debug_init(void) { 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("cputemp", 0644, tegra_soctherm_root, NULL, &cputemp_fops); debugfs_create_file("gputemp", 0644, tegra_soctherm_root, NULL, &gputemp_fops); debugfs_create_file("memtemp", 0644, tegra_soctherm_root, NULL, &memtemp_fops); debugfs_create_file("plltemp", 0644, tegra_soctherm_root, NULL, &plltemp_fops); debugfs_create_file("tempoverride", 0644, tegra_soctherm_root, NULL, &tempoverride_fops); debugfs_create_file("temp_log", 0644, tegra_soctherm_root, NULL, &temp_log_fops); return 0; } late_initcall(soctherm_debug_init); #endif