| /* |
| * 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"); |