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/io.h> 26 #include <asm/reg.h> 27 #include <asm/nvram.h> 28 #include <asm/cache.h> 29 #include <asm/8xx_immap.h> 30 #include <asm/machdep.h> 31 #include <asm/asm-prototypes.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 void TAUException(struct pt_regs * regs) 104 { 105 int cpu = smp_processor_id(); 106 107 irq_enter(); 108 tau[cpu].interrupts++; 109 110 TAUupdate(cpu); 111 112 irq_exit(); 113 } 114 #endif /* CONFIG_TAU_INT */ 115 116 static void tau_timeout(void * info) 117 { 118 int cpu; 119 int size; 120 int shrink; 121 122 cpu = smp_processor_id(); 123 124 if (!tau_int_enable) 125 TAUupdate(cpu); 126 127 /* Stop thermal sensor comparisons and interrupts */ 128 mtspr(SPRN_THRM3, 0); 129 130 size = tau[cpu].high - tau[cpu].low; 131 if (size > min_window && ! tau[cpu].grew) { 132 /* do an exponential shrink of half the amount currently over size */ 133 shrink = (2 + size - min_window) / 4; 134 if (shrink) { 135 tau[cpu].low += shrink; 136 tau[cpu].high -= shrink; 137 } else { /* size must have been min_window + 1 */ 138 tau[cpu].low += 1; 139 #if 1 /* debug */ 140 if ((tau[cpu].high - tau[cpu].low) != min_window){ 141 printk(KERN_ERR "temp.c: line %d, logic error\n", __LINE__); 142 } 143 #endif 144 } 145 } 146 147 tau[cpu].grew = 0; 148 149 set_thresholds(cpu); 150 151 /* Restart thermal sensor comparisons and interrupts. 152 * The "PowerPC 740 and PowerPC 750 Microprocessor Datasheet" 153 * recommends that "the maximum value be set in THRM3 under all 154 * conditions." 155 */ 156 mtspr(SPRN_THRM3, THRM3_SITV(0x1fff) | THRM3_E); 157 } 158 159 static struct workqueue_struct *tau_workq; 160 161 static void tau_work_func(struct work_struct *work) 162 { 163 msleep(shrink_timer); 164 on_each_cpu(tau_timeout, NULL, 0); 165 /* schedule ourselves to be run again */ 166 queue_work(tau_workq, work); 167 } 168 169 DECLARE_WORK(tau_work, tau_work_func); 170 171 /* 172 * setup the TAU 173 * 174 * Set things up to use THRM1 as a temperature lower bound, and THRM2 as an upper bound. 175 * Start off at zero 176 */ 177 178 int tau_initialized = 0; 179 180 static void __init TAU_init_smp(void *info) 181 { 182 unsigned long cpu = smp_processor_id(); 183 184 /* set these to a reasonable value and let the timer shrink the 185 * window */ 186 tau[cpu].low = 5; 187 tau[cpu].high = 120; 188 189 set_thresholds(cpu); 190 } 191 192 static int __init TAU_init(void) 193 { 194 /* We assume in SMP that if one CPU has TAU support, they 195 * all have it --BenH 196 */ 197 if (!cpu_has_feature(CPU_FTR_TAU)) { 198 printk("Thermal assist unit not available\n"); 199 tau_initialized = 0; 200 return 1; 201 } 202 203 tau_int_enable = IS_ENABLED(CONFIG_TAU_INT) && 204 !strcmp(cur_cpu_spec->platform, "ppc750"); 205 206 tau_workq = alloc_workqueue("tau", WQ_UNBOUND, 1, 0); 207 if (!tau_workq) 208 return -ENOMEM; 209 210 on_each_cpu(TAU_init_smp, NULL, 0); 211 212 queue_work(tau_workq, &tau_work); 213 214 pr_info("Thermal assist unit using %s, shrink_timer: %d ms\n", 215 tau_int_enable ? "interrupts" : "workqueue", shrink_timer); 216 tau_initialized = 1; 217 218 return 0; 219 } 220 221 __initcall(TAU_init); 222 223 /* 224 * return current temp 225 */ 226 227 u32 cpu_temp_both(unsigned long cpu) 228 { 229 return ((tau[cpu].high << 16) | tau[cpu].low); 230 } 231 232 u32 cpu_temp(unsigned long cpu) 233 { 234 return ((tau[cpu].high + tau[cpu].low) / 2); 235 } 236 237 u32 tau_interrupts(unsigned long cpu) 238 { 239 return (tau[cpu].interrupts); 240 } 241