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