1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * temp.c Thermal management for cpu's with Thermal Assist Units 4 * 5 * Written by Troy Benjegerdes <hozer@drgw.net> 6 * 7 * TODO: 8 * dynamic power management to limit peak CPU temp (using ICTC) 9 * calibration??? 10 * 11 * Silly, crazy ideas: use cpu load (from scheduler) and ICTC to extend battery 12 * life in portables, and add a 'performance/watt' metric somewhere in /proc 13 */ 14 15 #include <linux/errno.h> 16 #include <linux/kernel.h> 17 #include <linux/param.h> 18 #include <linux/string.h> 19 #include <linux/mm.h> 20 #include <linux/interrupt.h> 21 #include <linux/init.h> 22 #include <linux/delay.h> 23 #include <linux/workqueue.h> 24 25 #include <asm/interrupt.h> 26 #include <asm/io.h> 27 #include <asm/reg.h> 28 #include <asm/nvram.h> 29 #include <asm/cache.h> 30 #include <asm/8xx_immap.h> 31 #include <asm/machdep.h> 32 33 #include "setup.h" 34 35 static struct tau_temp 36 { 37 int interrupts; 38 unsigned char low; 39 unsigned char high; 40 unsigned char grew; 41 } tau[NR_CPUS]; 42 43 static bool tau_int_enable; 44 45 /* TODO: put these in a /proc interface, with some sanity checks, and maybe 46 * dynamic adjustment to minimize # of interrupts */ 47 /* configurable values for step size and how much to expand the window when 48 * we get an interrupt. These are based on the limit that was out of range */ 49 #define step_size 2 /* step size when temp goes out of range */ 50 #define window_expand 1 /* expand the window by this much */ 51 /* configurable values for shrinking the window */ 52 #define shrink_timer 2000 /* period between shrinking the window */ 53 #define min_window 2 /* minimum window size, degrees C */ 54 55 static void set_thresholds(unsigned long cpu) 56 { 57 u32 maybe_tie = tau_int_enable ? THRM1_TIE : 0; 58 59 /* setup THRM1, threshold, valid bit, interrupt when below threshold */ 60 mtspr(SPRN_THRM1, THRM1_THRES(tau[cpu].low) | THRM1_V | maybe_tie | THRM1_TID); 61 62 /* setup THRM2, threshold, valid bit, interrupt when above threshold */ 63 mtspr(SPRN_THRM2, THRM1_THRES(tau[cpu].high) | THRM1_V | maybe_tie); 64 } 65 66 static void TAUupdate(int cpu) 67 { 68 u32 thrm; 69 u32 bits = THRM1_TIV | THRM1_TIN | THRM1_V; 70 71 /* if both thresholds are crossed, the step_sizes cancel out 72 * and the window winds up getting expanded twice. */ 73 thrm = mfspr(SPRN_THRM1); 74 if ((thrm & bits) == bits) { 75 mtspr(SPRN_THRM1, 0); 76 77 if (tau[cpu].low >= step_size) { 78 tau[cpu].low -= step_size; 79 tau[cpu].high -= (step_size - window_expand); 80 } 81 tau[cpu].grew = 1; 82 pr_debug("%s: low threshold crossed\n", __func__); 83 } 84 thrm = mfspr(SPRN_THRM2); 85 if ((thrm & bits) == bits) { 86 mtspr(SPRN_THRM2, 0); 87 88 if (tau[cpu].high <= 127 - step_size) { 89 tau[cpu].low += (step_size - window_expand); 90 tau[cpu].high += step_size; 91 } 92 tau[cpu].grew = 1; 93 pr_debug("%s: high threshold crossed\n", __func__); 94 } 95 } 96 97 #ifdef CONFIG_TAU_INT 98 /* 99 * TAU interrupts - called when we have a thermal assist unit interrupt 100 * with interrupts disabled 101 */ 102 103 DEFINE_INTERRUPT_HANDLER_ASYNC(TAUException) 104 { 105 int cpu = smp_processor_id(); 106 107 tau[cpu].interrupts++; 108 109 TAUupdate(cpu); 110 } 111 #endif /* CONFIG_TAU_INT */ 112 113 static void tau_timeout(void * info) 114 { 115 int cpu; 116 int size; 117 int shrink; 118 119 cpu = smp_processor_id(); 120 121 if (!tau_int_enable) 122 TAUupdate(cpu); 123 124 /* Stop thermal sensor comparisons and interrupts */ 125 mtspr(SPRN_THRM3, 0); 126 127 size = tau[cpu].high - tau[cpu].low; 128 if (size > min_window && ! tau[cpu].grew) { 129 /* do an exponential shrink of half the amount currently over size */ 130 shrink = (2 + size - min_window) / 4; 131 if (shrink) { 132 tau[cpu].low += shrink; 133 tau[cpu].high -= shrink; 134 } else { /* size must have been min_window + 1 */ 135 tau[cpu].low += 1; 136 #if 1 /* debug */ 137 if ((tau[cpu].high - tau[cpu].low) != min_window){ 138 printk(KERN_ERR "temp.c: line %d, logic error\n", __LINE__); 139 } 140 #endif 141 } 142 } 143 144 tau[cpu].grew = 0; 145 146 set_thresholds(cpu); 147 148 /* Restart thermal sensor comparisons and interrupts. 149 * The "PowerPC 740 and PowerPC 750 Microprocessor Datasheet" 150 * recommends that "the maximum value be set in THRM3 under all 151 * conditions." 152 */ 153 mtspr(SPRN_THRM3, THRM3_SITV(0x1fff) | THRM3_E); 154 } 155 156 static struct workqueue_struct *tau_workq; 157 158 static void tau_work_func(struct work_struct *work) 159 { 160 msleep(shrink_timer); 161 on_each_cpu(tau_timeout, NULL, 0); 162 /* schedule ourselves to be run again */ 163 queue_work(tau_workq, work); 164 } 165 166 static DECLARE_WORK(tau_work, tau_work_func); 167 168 /* 169 * setup the TAU 170 * 171 * Set things up to use THRM1 as a temperature lower bound, and THRM2 as an upper bound. 172 * Start off at zero 173 */ 174 175 int tau_initialized = 0; 176 177 static void __init TAU_init_smp(void *info) 178 { 179 unsigned long cpu = smp_processor_id(); 180 181 /* set these to a reasonable value and let the timer shrink the 182 * window */ 183 tau[cpu].low = 5; 184 tau[cpu].high = 120; 185 186 set_thresholds(cpu); 187 } 188 189 static int __init TAU_init(void) 190 { 191 /* We assume in SMP that if one CPU has TAU support, they 192 * all have it --BenH 193 */ 194 if (!cpu_has_feature(CPU_FTR_TAU)) { 195 printk("Thermal assist unit not available\n"); 196 tau_initialized = 0; 197 return 1; 198 } 199 200 tau_int_enable = IS_ENABLED(CONFIG_TAU_INT) && 201 !strcmp(cur_cpu_spec->platform, "ppc750"); 202 203 tau_workq = alloc_ordered_workqueue("tau", 0); 204 if (!tau_workq) 205 return -ENOMEM; 206 207 on_each_cpu(TAU_init_smp, NULL, 0); 208 209 queue_work(tau_workq, &tau_work); 210 211 pr_info("Thermal assist unit using %s, shrink_timer: %d ms\n", 212 tau_int_enable ? "interrupts" : "workqueue", shrink_timer); 213 tau_initialized = 1; 214 215 return 0; 216 } 217 218 __initcall(TAU_init); 219 220 /* 221 * return current temp 222 */ 223 224 u32 cpu_temp_both(unsigned long cpu) 225 { 226 return ((tau[cpu].high << 16) | tau[cpu].low); 227 } 228 229 u32 cpu_temp(unsigned long cpu) 230 { 231 return ((tau[cpu].high + tau[cpu].low) / 2); 232 } 233 234 u32 tau_interrupts(unsigned long cpu) 235 { 236 return (tau[cpu].interrupts); 237 } 238