blob: 0a72c738b73824b175bef6dde1222bd7dab77122 [file] [log] [blame]
/*
* asr18xx_thermal.c - ASR 1803/1828 TMU (Thermal Management Unit)
*
* Author: Lianghu Xu <lianghuxu@asrmicro.com>
* Copyright: (C) 2019 ASR Microelectronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/thermal.h>
#include <linux/clk/mmp.h>
#include <linux/of.h>
#ifdef CONFIG_CPU_FREQ
#include <linux/cpufreq.h>
#include <linux/cpu_cooling.h>
#endif
#include <linux/cooling_dev_asr.h>
#include <linux/delay.h>
#include <linux/cpu.h>
#include <linux/pm_qos.h>
#include <linux/cputype.h>
#include <linux/devfreq.h>
#include <soc/asr/regs-addr.h>
#include <soc/asr/addr-map.h>
#include "asr18xx_voltage.h"
#define APB_CLK_BASE (0xd4015000)
#define TSEN_PCTRL (0x0)
#define TSEN_INT_CLR (0x4)
#define TSEN_INT_MASK (0x8)
#define TSEN_INT_STATUS (0x10)
#define TSEN_READ_DATA0 (0x14)
#define TSEN_TIME_CTRL (0x18)
#define TSEN_TEMP_THRESHOLD0 (0x20)
#define EMERGENT_REBOOT_TEMP_THR (0x30)
#define TSEN_AUTO_MODE_INTERVAL (0x34)
/* TSEN_PCTRL */
#define SENSOR0_EN_BIT (1 << 24)
#define HW_AUTO_MODE (1 << 23)
#define TSEN_RAW_SEL (1 << 7)
#define TEMP_MODE (1 << 3)
#define TSEN_EN_SENSOR (1 << 0)
/* TSEN_INT_MASK */
#define INT_MASK_SOFT_TEST (1 << 0)
#define TSEN0_D_INT_MASK (1 << 1)
#define TSEN0_U_INT_MASK (1 << 2)
#define EMERGENT_REBOOT_TEMP_MASK (1 << 9)
/* TSEN_INT_CLR */
#define INT_CLR_SOFT_TEST (1 << 0)
#define TSEN0_D_INT_CLR (1 << 1)
#define TSEN0_U_INT_CLR (1 << 2)
#define EMERGENT_REBOOT_TEMP_CLR (1 << 9)
#define MPMU_CRSR (0x28)
#define CRSR_WDTR_TSEN (0x1 << 4)
#define reg_read(off) readl(thermal_dev.base + (off))
#define reg_write(val, off) writel((val), thermal_dev.base + (off))
#define reg_clr_set(off, clr, set) \
reg_write(((reg_read(off) & ~(clr)) | (set)), off)
enum trip_points {
TRIP_POINT_0,
TRIP_POINT_1,
TRIP_POINT_2,
TRIP_POINT_3,
TRIP_POINT_4,
TRIP_POINT_5,
TRIP_POINT_6,
TRIP_POINT_7,
TRIP_POINTS_NUM,
TRIP_POINTS_ACTIVE_NUM = TRIP_POINTS_NUM - 1,
};
struct cooling_device {
struct thermal_cooling_device *combile_cool;
int max_state, cur_state;
struct thermal_cooling_device *cool_cpufreq;
unsigned long cpufreq_cstate[THERMAL_MAX_TRIPS];
/* voltage based cooling state from throttle table */
struct thermal_cooling_device *cool_ddrfreq;
};
struct asr18xx_thermal_device {
struct thermal_zone_device *therm_cpu;
int trip_range;
enum thermal_trend trip_trend;
struct resource *mem;
void __iomem *base;
struct clk *therm_clk;
struct cooling_device cdev;
struct pxa_voltage_thermal thermal_volt;
struct delayed_work resume_work;
int hit_trip_cnt[TRIP_POINTS_NUM];
int irq;
bool from_resume;
int temp;
};
static struct asr18xx_thermal_device thermal_dev;
extern enum max_corefreq_type max_corefreq_mode;
/*
*the thermal state number mapping to frequency in descending order,
*the state number n stands for the n-th freq beginning from the highest freq
*/
unsigned int asr_tsen_throttle_tbl_832M[][THROTTLE_NUM][THERMAL_MAX_TRIPS+1] = {
[POWER_SAVING_MODE] = {
[THROTTLE_VL] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
[THROTTLE_CORE] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
//0, 0, 0, 0, 1, 1, 1, 2
},
[THROTTLE_DDR] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
},
[BENCHMARK_MODE] = {
[THROTTLE_VL] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
[THROTTLE_CORE] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
//0, 0, 0, 0, 0, 1, 1, 1
},
[THROTTLE_DDR] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
},
};
unsigned int asr_tsen_throttle_tbl_1248M[][THROTTLE_NUM][THERMAL_MAX_TRIPS+1] = {
[POWER_SAVING_MODE] = {
[THROTTLE_VL] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
[THROTTLE_CORE] = { 0,
0, 0, 0, 0, 0, 0, 0, 1
//0, 0, 0, 1, 1, 2, 2, 2
},
[THROTTLE_DDR] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
},
[BENCHMARK_MODE] = {
[THROTTLE_VL] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
[THROTTLE_CORE] = { 0,
0, 0, 0, 0, 0, 0, 0, 1
//0, 0, 0, 1, 1, 2, 2, 2
},
[THROTTLE_DDR] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
},
};
unsigned int asr_tsen_throttle_tbl_1500M[][THROTTLE_NUM][THERMAL_MAX_TRIPS+1] = {
[POWER_SAVING_MODE] = {
[THROTTLE_VL] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
[THROTTLE_CORE] = { 0,
0, 0, 0, 0, 0, 1, 1, 2
//0, 0, 0, 1, 1, 2, 2, 2
},
[THROTTLE_DDR] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
},
[BENCHMARK_MODE] = {
[THROTTLE_VL] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
[THROTTLE_CORE] = { 0,
0, 0, 0, 0, 0, 0, 0, 1
//0, 0, 0, 1, 1, 2, 2, 2
},
[THROTTLE_DDR] = { 0,
0, 0, 0, 0, 0, 0, 0, 0
},
},
};
static int trips_temp_benchmark[TRIP_POINTS_NUM] = {
87000, /* TRIP_POINT_0 */
92000, /* TRIP_POINT_1 */
97000, /* TRIP_POINT_2 */
100000, /* TRIP_POINT_3 */
103000, /* TRIP_POINT_4 */
106000, /* TRIP_POINT_5 */
109000, /* TRIP_POINT_6 */
112000, /* TRIP_POINT_7 */
};
static int trips_hyst_benchmark[TRIP_POINTS_NUM] = {
84000, /* TRIP_POINT_0 */
89000, /* TRIP_POINT_1 */
94000, /* TRIP_POINT_2 */
97000, /* TRIP_POINT_3 */
100000, /* TRIP_POINT_4 */
103000, /* TRIP_POINT_5 */
106000, /* TRIP_POINT_6 */
112000, /* TRIP_POINT_7 */
};
/*
*rule of thumb:hyst=temp-3; bench=powersave+2; trip range >=2
*/
static int trips_temp_powersave[TRIP_POINTS_NUM] = {
85000, /* TRIP_POINT_0 */
90000, /* TRIP_POINT_1 */
95000, /* TRIP_POINT_2 */
98000, /* TRIP_POINT_3 */
101000, /* TRIP_POINT_4 */
104000, /* TRIP_POINT_5 */
107000, /* TRIP_POINT_6 */
110000, /* TRIP_POINT_7 */
};
static int trips_hyst_powersave[TRIP_POINTS_NUM] = {
82000, /* TRIP_POINT_0_D */
87000, /* TRIP_POINT_1_D */
92000, /* TRIP_POINT_2_D */
95000, /* TRIP_POINT_3_D */
98000, /* TRIP_POINT_4_D */
101000, /* TRIP_POINT_5_D */
104000, /* TRIP_POINT_6_D */
110000, /* TRIP_POINT_7_D */
};
#define EMERGENT_REBOOT_TEMP 115000
#define THSEN_GAIN 1
#define THSEN_OFFSET 281
#define THSEN_OFFSET_FLCN_KAGU 275
static bool asr1803_soc32k_is_from_rtc32k = false;
static int asr18xx_set_threshold(int range);
static int millicelsius_decode(u32 tcode)
{
int cels;
if (cpu_is_asr1803() || cpu_is_asr1828())
cels = (int)(tcode * THSEN_GAIN - THSEN_OFFSET_FLCN_KAGU);
else
cels = (int)(tcode * THSEN_GAIN - THSEN_OFFSET);
return cels * 1000;
}
static int millicelsius_encode(int mcels)
{
u32 tcode;
mcels /= 1000;
if (cpu_is_asr1803() || cpu_is_asr1828())
tcode = (mcels + THSEN_OFFSET_FLCN_KAGU) / (THSEN_GAIN);
else
tcode = (mcels + THSEN_OFFSET) / (THSEN_GAIN);
return tcode;
}
static ssize_t hit_trip_status_get(struct device *dev,
struct device_attribute *attr, char *buf)
{
int i;
int ret = 0;
u32 tmp;
ret += sprintf(buf + ret, "Register dump:\n");
ret += sprintf(buf + ret, "TSEN_PCTRL=0x%x\n", reg_read(TSEN_PCTRL));
ret += sprintf(buf + ret, "TSEN_TIME_CTRL=0x%x\n", reg_read(TSEN_TIME_CTRL));
tmp = reg_read(TSEN_READ_DATA0);
ret += sprintf(buf + ret, "TSEN_READ_DATA0=0x%x(data0:%dmC)\n",
tmp, millicelsius_decode(tmp & 0xFFFF));
tmp = reg_read(EMERGENT_REBOOT_TEMP_THR);
ret += sprintf(buf + ret, "EMERGENT_REBOOT_TEMP_THR=0x%x(%dmC)\n",
tmp, millicelsius_decode(tmp & 0xFFFF));
tmp = reg_read(TSEN_TEMP_THRESHOLD0);
ret += sprintf(buf + ret, "TSEN_TEMP_THRESHOLD0=0x%x(tsen0_u:%dmC, tsen0_d:%dmC)\n",
tmp, millicelsius_decode(tmp >> 16), millicelsius_decode(tmp & 0xFFFF));
ret += sprintf(buf + ret, "thermal_dev.from_resume=%d\n", thermal_dev.from_resume);
for (i = 0; i < TRIP_POINTS_NUM; i++) {
ret += sprintf(buf + ret, "trip %d: %d hits\n",
thermal_dev.thermal_volt.tsen_trips_temp
[thermal_dev.thermal_volt.therm_policy][i],
thermal_dev.hit_trip_cnt[i]);
}
return ret;
}
static DEVICE_ATTR(hit_trip_status, 0444, hit_trip_status_get, NULL);
static struct attribute *thermal_attrs[] = {
&dev_attr_hit_trip_status.attr,
NULL,
};
static struct attribute_group thermal_attr_grp = {
.attrs = thermal_attrs,
};
static int cpu_sys_get_temp(struct thermal_zone_device *thermal,
int *temp)
{
u32 tmp;
if (!((reg_read(TSEN_PCTRL)) & TSEN_EN_SENSOR)) {
printk_ratelimited("tsen not enabled on read\n");
*temp = 0;
return 0;
}
tmp = reg_read(TSEN_READ_DATA0);
*temp = millicelsius_decode(tmp & 0xFFFF);
/* unreasonable temperature, add tolerance for out of range temperature */
if (*temp < -45000) {
printk_ratelimited("tsen error temp: %d 0x%x\n", *temp, tmp);
*temp = -45000;
}
if (*temp > 130000) {
printk_ratelimited("tsen error temp: %d 0x%x\n", *temp, tmp);
*temp = 130000;
}
return 0;
}
static int cpu_sys_get_trip_type(struct thermal_zone_device *thermal, int trip,
enum thermal_trip_type *type)
{
if ((trip >= 0) && (trip < TRIP_POINTS_ACTIVE_NUM))
*type = THERMAL_TRIP_ACTIVE;
else if (TRIP_POINTS_ACTIVE_NUM == trip)
*type = THERMAL_TRIP_CRITICAL;
else
*type = (enum thermal_trip_type)(-1);
return 0;
}
static int cpu_sys_get_trip_temp(struct thermal_zone_device *thermal, int trip,
int *temp)
{
if ((trip >= 0) && (trip < TRIP_POINTS_NUM))
*temp = thermal_dev.thermal_volt.tsen_trips_temp
[thermal_dev.thermal_volt.therm_policy][trip];
else
*temp = -1;
return 0;
}
static int cpu_sys_get_trip_hyst(struct thermal_zone_device *thermal,
int trip, int *temp)
{
if ((trip >= 0) && (trip < TRIP_POINTS_NUM))
*temp = thermal_dev.thermal_volt.tsen_trips_temp_d
[thermal_dev.thermal_volt.therm_policy][trip];
else
*temp = -1;
return 0;
}
static int cpu_sys_set_trip_temp(struct thermal_zone_device *thermal, int trip,
int temp)
{
struct asr18xx_thermal_device *cpu_thermal = thermal->devdata;
if ((trip >= 0) && (trip < TRIP_POINTS_NUM))
thermal_dev.thermal_volt.tsen_trips_temp
[thermal_dev.thermal_volt.therm_policy][trip] = temp;
if(trip == cpu_thermal->trip_range)
asr18xx_set_threshold(cpu_thermal->trip_range);
return 0;
}
static int cpu_sys_set_trip_hyst(struct thermal_zone_device *thermal,
int trip, int temp)
{
struct asr18xx_thermal_device *cpu_thermal = thermal->devdata;
if ((trip >= 0) && (trip < TRIP_POINTS_ACTIVE_NUM))
thermal_dev.thermal_volt.tsen_trips_temp_d
[thermal_dev.thermal_volt.therm_policy][trip] = temp;
if ((TRIP_POINTS_NUM - 1) == trip)
pr_warn("critical down doesn't used\n");
else if(trip == cpu_thermal->trip_range)
asr18xx_set_threshold(cpu_thermal->trip_range);
return 0;
}
static int cpu_sys_get_crit_temp(struct thermal_zone_device *thermal,
int *temp)
{
return thermal_dev.thermal_volt.tsen_trips_temp
[thermal_dev.thermal_volt.therm_policy][TRIP_POINTS_NUM - 1];
}
/* Get the temperature trend */
static int cpu_sys_get_trend(struct thermal_zone_device *thermal,
int trip, enum thermal_trend *trend)
{
int ret;
int temp, trip_temp, trip_hyst;
ret = cpu_sys_get_trip_temp(thermal, trip, &trip_temp) ||
cpu_sys_get_trip_hyst(thermal, trip, &trip_hyst) ||
cpu_sys_get_temp(thermal, &temp);
if (ret < 0)
return ret;
if (temp >= trip_temp)
*trend = THERMAL_TREND_RAISING;
else if (temp <= trip_hyst)
*trend = THERMAL_TREND_DROPPING;
else
*trend = THERMAL_TREND_STABLE;
return 0;
}
static struct thermal_zone_device_ops cpu_thermal_ops = {
.get_temp = cpu_sys_get_temp,
.get_trip_type = cpu_sys_get_trip_type,
.get_trip_temp = cpu_sys_get_trip_temp,
.get_trip_hyst = cpu_sys_get_trip_hyst,
.set_trip_temp = cpu_sys_set_trip_temp,
.set_trip_hyst = cpu_sys_set_trip_hyst,
.get_crit_temp = cpu_sys_get_crit_temp,
.get_trend = cpu_sys_get_trend,
};
#ifdef CONFIG_PM_SLEEP
#define ASR1803_APB_SPARE5 (APB_VIRT_BASE + 0x00090110)
#define ASR1803_MPMU_SCCR (MPMU_REG(0x38))
extern bool asr1803_is_fuse_a3(void);
static void asr1803_set_32k_from_rtc32k(void)
{
u32 value;
value = readl(ASR1803_MPMU_SCCR);
pr_info("MPMU_SCCR: 0x%x\n", value);
value |= (0x1 << 0);
writel(value, ASR1803_MPMU_SCCR);
value = readl(ASR1803_APB_SPARE5);
value &= ~(0x1 << 31);
writel(value, ASR1803_APB_SPARE5);
asr1803_soc32k_is_from_rtc32k = true;
pr_info("!!!!!!!!!!!asr1803 32k set to rtc32k\n");
}
void __asr1803_set_32k_to_rtc32k(void)
{
int temp;
/* restore 32k to be generated from rtc32k */
if ((!asr1803_soc32k_is_from_rtc32k) && cpu_is_asr1803_a1() && (!asr1803_is_fuse_a3())) {
cpu_sys_get_temp(thermal_dev.therm_cpu, &temp);
if (temp > (-10 * 1000)) {
pr_info("temp: %d, set rtc32k\n", temp);
asr1803_set_32k_from_rtc32k();
}
}
}
void asr1803_set_32k_to_rtc32k(void)
{
if (!(cpu_is_asr1803()))
return;
if (asr1803_soc32k_is_from_rtc32k)
return;
if (thermal_dev.from_resume) {
pr_info("%s: from_resume: %d\n", __func__, thermal_dev.from_resume);
return;
}
__asr1803_set_32k_to_rtc32k();
}
static int thermal_suspend(struct device *dev)
{
/* restore 32k to be generated from rtc32k */
__asr1803_set_32k_to_rtc32k();
cancel_delayed_work_sync(&thermal_dev.resume_work);
thermal_dev.from_resume = false;
reg_clr_set(TSEN_PCTRL, TSEN_EN_SENSOR, 0);
return 0;
}
static void thermal_delayed_resume(struct work_struct *work)
{
int temp;
if(thermal_dev.from_resume)
{
reg_clr_set(TSEN_PCTRL, 0, TSEN_EN_SENSOR);
cpu_sys_get_temp(thermal_dev.therm_cpu, &temp);
if (!temp)
WARN_ON("tsen delayed resume fail\n");
thermal_dev.from_resume = false;
reg_clr_set(TSEN_INT_MASK, EMERGENT_REBOOT_TEMP_MASK, 0);
/* restore 32k to be generated from rtc32k */
__asr1803_set_32k_to_rtc32k();
}
}
static int thermal_resume(struct device *dev)
{
thermal_dev.from_resume = true;
reg_clr_set(TSEN_INT_MASK, 0, EMERGENT_REBOOT_TEMP_MASK);
schedule_delayed_work(&thermal_dev.resume_work, HZ/4);
/* hold 1s wake lock to make sure the resume work is invoked
* before system goes to suspend again
*/
pm_wakeup_event(dev, 1000);
return 0;
}
static SIMPLE_DEV_PM_OPS(thermal_pm_ops,
thermal_suspend, thermal_resume);
#define PXA_TMU_PM (&thermal_pm_ops)
#else
#define PXA_TMU_PM NULL
#endif
static int combile_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct asr18xx_thermal_device *cpu_thermal = cdev->devdata;
*state = cpu_thermal->cdev.max_state;
return 0;
}
static int combile_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct asr18xx_thermal_device *cpu_thermal = cdev->devdata;
*state = cpu_thermal->cdev.cur_state;
return 0;
}
static int combile_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct asr18xx_thermal_device *cpu_thermal = cdev->devdata;
struct thermal_cooling_device *c_freq = cpu_thermal->cdev.cool_cpufreq;
struct thermal_cooling_device *ddr_freq = cpu_thermal->cdev.cool_ddrfreq;
unsigned long freq_state = 0;
unsigned long ddr_freq_state = 0;
int temp = 0;
if (state > cpu_thermal->cdev.max_state)
return -EINVAL;
cpu_thermal->cdev.cur_state = state;
cpu_sys_get_temp(thermal_dev.therm_cpu, &temp);
freq_state = thermal_dev.thermal_volt.tsen_throttle_tbl
[thermal_dev.thermal_volt.therm_policy][THROTTLE_CORE][state + 1];
if (c_freq)
c_freq->ops->set_cur_state(c_freq, freq_state);
ddr_freq_state = thermal_dev.thermal_volt.tsen_throttle_tbl
[thermal_dev.thermal_volt.therm_policy][THROTTLE_DDR][state + 1];
if (ddr_freq)
ddr_freq->ops->set_cur_state(ddr_freq, ddr_freq_state);
pr_info("Thermal cpu temp %dC, state %ld, cpufreq %dkHz, ddrfreq %dkHz\n",
temp / 1000, state,
thermal_dev.thermal_volt.cpufreq_tbl.freq_tbl[freq_state],
thermal_dev.thermal_volt.ddrfreq_tbl.freq_tbl[ddr_freq_state]);
return 0;
}
static struct thermal_cooling_device_ops const combile_cooling_ops = {
.get_max_state = combile_get_max_state,
.get_cur_state = combile_get_cur_state,
.set_cur_state = combile_set_cur_state,
};
static void asr18xx_register_thermal(void)
{
int i, trip_w_mask = 0;
thermal_dev.cdev.cool_cpufreq = cpufreq_cool_register();
thermal_dev.cdev.cool_ddrfreq = ddrfreq_cool_register();
thermal_dev.cdev.combile_cool = thermal_cooling_device_register(
"cpu-combile-cool", &thermal_dev, &combile_cooling_ops);
thermal_dev.cdev.max_state = TRIP_POINTS_ACTIVE_NUM;
thermal_dev.cdev.cur_state = 0;
for (i = 0; i < TRIP_POINTS_NUM; i++)
trip_w_mask |= (1 << i);
thermal_dev.therm_cpu = thermal_zone_device_register(
"tsen_max", TRIP_POINTS_NUM, trip_w_mask,
&thermal_dev, &cpu_thermal_ops, NULL, 0, 0);
thermal_dev.thermal_volt.therm_max = thermal_dev.therm_cpu;
/*
* enable bi_direction state machine, then it didn't care
* whether up/down trip points are crossed or not
*/
thermal_dev.therm_cpu->tzdctrl.state_ctrl = true;
/* bind combile cooling */
thermal_zone_bind_cooling_device(thermal_dev.therm_cpu,
TRIP_POINT_0, thermal_dev.cdev.combile_cool,
THERMAL_NO_LIMIT,
THERMAL_NO_LIMIT,
THERMAL_WEIGHT_DEFAULT);
i = sysfs_create_group(&((thermal_dev.therm_cpu->device).kobj),
&thermal_attr_grp);
if (i < 0)
pr_err("Failed to register private thermal interface\n");
}
static int asr18xx_set_threshold(int range)
{
u32 tmp;
if (range < 0 || range > TRIP_POINTS_ACTIVE_NUM) {
pr_err("soc thermal: invalid threshold %d\n", range);
return -1;
}
if (0 == range) {
tmp = (millicelsius_encode(thermal_dev.thermal_volt.tsen_trips_temp
[thermal_dev.thermal_volt.therm_policy][0]) << 16) & 0xFFFF0000;
reg_clr_set(TSEN_TEMP_THRESHOLD0, 0xFFFF0000, tmp);
tmp = millicelsius_encode(thermal_dev.thermal_volt.tsen_trips_temp_d
[thermal_dev.thermal_volt.therm_policy][0]) & 0xFFFF;
reg_clr_set(TSEN_TEMP_THRESHOLD0, 0xFFFF, tmp);
reg_clr_set(TSEN_INT_MASK, TSEN0_U_INT_MASK, 0);
reg_clr_set(TSEN_INT_MASK, 0, TSEN0_D_INT_MASK);
reg_clr_set(TSEN_INT_CLR, 0, TSEN0_D_INT_CLR);
} else {
tmp = (millicelsius_encode(thermal_dev.thermal_volt.tsen_trips_temp
[thermal_dev.thermal_volt.therm_policy][range]) << 16) & 0xFFFF0000;
reg_clr_set(TSEN_TEMP_THRESHOLD0, 0xFFFF0000, tmp);
tmp = millicelsius_encode(thermal_dev.thermal_volt.tsen_trips_temp_d
[thermal_dev.thermal_volt.therm_policy][range - 1]) & 0xFFFF;
reg_clr_set(TSEN_TEMP_THRESHOLD0, 0xFFFF, tmp);
reg_clr_set(TSEN_INT_MASK, TSEN0_U_INT_MASK, 0);
reg_clr_set(TSEN_INT_MASK, TSEN0_D_INT_MASK, 0);
}
return 0;
}
static void asr18xx_set_interval(int ms)
{
/* 32k clock, low 16bit */
int interval_val = ms * 32;
reg_clr_set(TSEN_AUTO_MODE_INTERVAL, 0xFFFF, interval_val & 0xFFFF);
}
static void mapping_cooling_dev(struct device_node *np)
{
int i;
if (cpu_is_asr1803()) {
thermal_dev.thermal_volt.tsen_throttle_tbl = asr_tsen_throttle_tbl_1248M;
} else {
if (max_corefreq_mode == CORE_1248M)
thermal_dev.thermal_volt.tsen_throttle_tbl = asr_tsen_throttle_tbl_1248M;
else if (max_corefreq_mode == CORE_1475M)
thermal_dev.thermal_volt.tsen_throttle_tbl = asr_tsen_throttle_tbl_1500M;
else
thermal_dev.thermal_volt.tsen_throttle_tbl = asr_tsen_throttle_tbl_832M;
}
thermal_dev.thermal_volt.therm_policy = POWER_SAVING_MODE;
thermal_dev.thermal_volt.range_max = TRIP_POINTS_ACTIVE_NUM;
strcpy(thermal_dev.thermal_volt.cpu_name, "asr18xx");
thermal_dev.thermal_volt.set_threshold = asr18xx_set_threshold;
thermal_dev.thermal_volt.vl_master = THROTTLE_CORE;
for (i = 0; i < TRIP_POINTS_NUM; i++) {
thermal_dev.thermal_volt.tsen_trips_temp
[POWER_SAVING_MODE][i] = trips_temp_powersave[i];
thermal_dev.thermal_volt.tsen_trips_temp_d
[POWER_SAVING_MODE][i] = trips_hyst_powersave[i];
thermal_dev.thermal_volt.tsen_trips_temp
[BENCHMARK_MODE][i] = trips_temp_benchmark[i];
thermal_dev.thermal_volt.tsen_trips_temp_d
[BENCHMARK_MODE][i] = trips_hyst_benchmark[i];
}
mutex_init(&thermal_dev.thermal_volt.policy_lock);
voltage_mrvl_init(&(thermal_dev.thermal_volt));
tsen_update_policy();
register_debug_interface();
tsen_policy_dump(NULL, 0);
return;
}
static irqreturn_t asr18xx_thread_irq(int irq, void *devid)
{
char data[10];
char direction[8];
char *tenvp[] = { data, direction, NULL };
pr_info("Tsen temp = %d\n", thermal_dev.temp);
if ((thermal_dev.trip_trend != THERMAL_TREND_STABLE) && (thermal_dev.from_resume == false)) {
/*
* trigger framework cooling, the real cooling behavior
* rely on governor, if it's user_space, then only uevent
* will be sent by framework, other wise, related governor
* will do real cooling
*/
thermal_zone_device_update(thermal_dev.therm_cpu, THERMAL_EVENT_UNSPECIFIED);
/* also notifies the user space through UEvents for user space specific cooling*/
snprintf(data, sizeof(data), "TSTAGE=%d", thermal_dev.trip_range);
snprintf(direction, sizeof(direction), "TREND=%d", thermal_dev.trip_trend);
kobject_uevent_env(&thermal_dev.therm_cpu->device.kobj, KOBJ_CHANGE, tenvp);
}
return IRQ_HANDLED;
}
static irqreturn_t asr18xx_irq(int irq, void *devid)
{
u32 tmp, mask;
int temp;
/*read the int status and clear the interrupts*/
tmp = reg_read(TSEN_INT_STATUS);
reg_clr_set(TSEN_INT_CLR, 0, tmp);
/*at resuming from suspend, the ADC is unstable in the first 100ms,
*it will get very high temp and trigger EMERGENT_REBOOT
*The trick is to filter out those noise by return at from_resume case,
*and use delayed resume worker to enable Tsensor
*/
if (thermal_dev.from_resume)
return IRQ_WAKE_THREAD;
/*record the mask value of last time*/
mask = reg_read(TSEN_INT_MASK);
/*mask both up and down interrupts to avoid noise interrupts before threshold updated*/
reg_clr_set(TSEN_INT_MASK, 0, TSEN0_U_INT_MASK | TSEN0_D_INT_MASK);
cpu_sys_get_temp(thermal_dev.therm_cpu, &temp);
thermal_dev.temp = temp;
thermal_dev.trip_trend = get_tz_trend(thermal_dev.therm_cpu, thermal_dev.trip_range);
//pr_info("previous trip_range = %d, temp trend = %d\n", thermal_dev.trip_range, thermal_dev.trip_trend);
if ((~(mask & TSEN0_U_INT_MASK)) && (thermal_dev.trip_trend == THERMAL_TREND_RAISING)) {
if (thermal_dev.trip_range == TRIP_POINTS_ACTIVE_NUM) {
/* wait framework shutdown */
pr_info("critical temp = %d\n", temp);
} else {
thermal_dev.hit_trip_cnt[thermal_dev.trip_range]++;
thermal_dev.trip_range++;
if (thermal_dev.trip_range > TRIP_POINTS_ACTIVE_NUM)
thermal_dev.trip_range = TRIP_POINTS_ACTIVE_NUM;
/*pass down the ball to next trip range*/
asr18xx_set_threshold(thermal_dev.trip_range);
}
}
if ((~(mask & TSEN0_D_INT_MASK)) && (thermal_dev.trip_trend == THERMAL_TREND_DROPPING)) {
thermal_dev.trip_range--;
if (thermal_dev.trip_range < 0)
thermal_dev.trip_range = 0;
asr18xx_set_threshold(thermal_dev.trip_range);
}
/* restore the int mask for THERMAL_TREND_STABLE */
if (thermal_dev.trip_trend == THERMAL_TREND_STABLE)
reg_write(mask, TSEN_INT_MASK);
if (unlikely((reg_read(TSEN_INT_MASK) & (TSEN0_D_INT_MASK | TSEN0_U_INT_MASK))
== (TSEN0_D_INT_MASK | TSEN0_U_INT_MASK))) {
pr_info("previous trip_range = %d, temp trend = %d\n",
thermal_dev.trip_range, thermal_dev.trip_trend);
if (thermal_dev.trip_range != TRIP_POINTS_ACTIVE_NUM)
BUG();
}
return IRQ_WAKE_THREAD;
}
static int asr18xx_thermal_probe(struct platform_device *pdev)
{
int ret = 0;
u32 tmp;
memset(&thermal_dev, 0, sizeof(thermal_dev));
thermal_dev.irq = platform_get_irq(pdev, 0);
if (thermal_dev.irq < 0) {
dev_err(&pdev->dev, "Failed to get platform irq\n");
return -EINVAL;
}
thermal_dev.mem =
platform_get_resource(pdev, IORESOURCE_MEM, 0);
thermal_dev.base =
devm_ioremap_resource(&pdev->dev, thermal_dev.mem);
if (IS_ERR(thermal_dev.base))
return PTR_ERR(thermal_dev.base);
ret = devm_request_threaded_irq(&pdev->dev, thermal_dev.irq,
asr18xx_irq, asr18xx_thread_irq, IRQF_ONESHOT,
pdev->name, NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to request irq: %d\n",
thermal_dev.irq);
return ret;
}
thermal_dev.from_resume = false;
thermal_dev.therm_clk = devm_clk_get(&pdev->dev, "tsen_clk");
if (IS_ERR(thermal_dev.therm_clk)) {
dev_err(&pdev->dev, "Could not get thermal clock\n");
return PTR_ERR(thermal_dev.therm_clk);
}
clk_prepare_enable(thermal_dev.therm_clk);
/* make sure clock stable */
usleep_range(20, 30);
/*This is fake since all registers are reset by the reboot*/
if (reg_read(TSEN_INT_STATUS) & EMERGENT_REBOOT_TEMP_MASK) {
pr_warn("System reset by thermal watch dog (%d C)\n",EMERGENT_REBOOT_TEMP/1000);
reg_clr_set(TSEN_INT_CLR, 0, EMERGENT_REBOOT_TEMP_CLR);
udelay(10);
}
tmp = readl(regs_addr_get_va(REGS_ADDR_MPMU) + MPMU_CRSR);
pr_warn("MPMU_CRSR: 0x%x\n", tmp);
if (tmp & CRSR_WDTR_TSEN) {
pr_warn("!!!!!!!!SOC reset by thermal watchdog (%d C)\n",
EMERGENT_REBOOT_TEMP/1000);
}
tmp = reg_read(TSEN_INT_STATUS);
if (tmp) {
void *apb_base = ioremap_nocache(APB_CLK_BASE, SZ_512);
if (apb_base) {
pr_warn("reinit thermal TSEN_INT_STATUS = 0x%x\n", tmp);
/* delay 10us for each step to ganrantee reset suc */
/* bit1 ctl reset, bit0 ctl enable */
writel(0x2, (apb_base + 0x6C));
udelay(10);
writel(0x0, (apb_base + 0x6C));
udelay(10);
writel(0x1, (apb_base + 0x6C));
udelay(10);
iounmap(apb_base);
tmp = reg_read(TSEN_INT_STATUS);
reg_clr_set(TSEN_INT_CLR, 0, tmp);
tmp = reg_read(TSEN_INT_STATUS);
if (tmp)
WARN_ON("reinit thermal failed\n");
}
} else
pr_info("thermal status fine\n");
/* init threshold */
mapping_cooling_dev(pdev->dev.of_node);
/* init thermal framework */
asr18xx_register_thermal();
/* set the EMERGENT_REBOOT_TEMP */
tmp = millicelsius_encode(EMERGENT_REBOOT_TEMP) & 0xFFFF;
reg_clr_set(EMERGENT_REBOOT_TEMP_THR, 0xFFFF, tmp);
reg_clr_set(TSEN_INT_MASK, EMERGENT_REBOOT_TEMP_MASK, 0);
/* enable the first trip-point0, kick off the ball */
thermal_dev.trip_range = 0;
asr18xx_set_threshold(thermal_dev.trip_range);
/* set auto interval 0ms */
asr18xx_set_interval(0);
/* set filter period time from 0x4000, i.e. ADC sampling time as 16ms */
reg_clr_set(TSEN_TIME_CTRL, 0x00FFFFFF, 0x00400044);
/* start hardware auto test mode */
reg_clr_set(TSEN_PCTRL, 0xFFFFFFFF, SENSOR0_EN_BIT|HW_AUTO_MODE|
TSEN_RAW_SEL|TEMP_MODE|TSEN_EN_SENSOR);
INIT_DELAYED_WORK(&thermal_dev.resume_work, thermal_delayed_resume);
device_init_wakeup(&pdev->dev, 1);
pr_info("asr18xx thermal probed\n");
return 0;
}
static int asr18xx_thermal_remove(struct platform_device *pdev)
{
reg_clr_set(TSEN_PCTRL, TSEN_EN_SENSOR, 0);
clk_disable_unprepare(thermal_dev.therm_clk);
cpufreq_cool_unregister(thermal_dev.cdev.cool_cpufreq);
thermal_cooling_device_unregister(thermal_dev.cdev.combile_cool);
thermal_zone_device_unregister(thermal_dev.therm_cpu);
pr_info("Kernel Thermal management unregistered\n");
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id asr18xx_tmu_match[] = {
{ .compatible = "asr,asr18xx-thermal", },
{},
};
MODULE_DEVICE_TABLE(of, asr18xx_tmu_match);
#endif
static struct platform_driver asr18xx_thermal_driver = {
.driver = {
.name = "asr18xx-thermal",
.pm = PXA_TMU_PM,
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(asr18xx_tmu_match),
#endif
},
.probe = asr18xx_thermal_probe,
.remove = asr18xx_thermal_remove,
};
module_platform_driver(asr18xx_thermal_driver);
MODULE_AUTHOR("ASR Microelectronics");
MODULE_DESCRIPTION("ASR18XX SoC thermal driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:asr18xx-thermal");