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(func: tau_timeout, NULL, wait: 0); |
162 | /* schedule ourselves to be run again */ |
163 | queue_work(wq: 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(func: TAU_init_smp, NULL, wait: 0); |
208 | |
209 | queue_work(wq: tau_workq, work: &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 | |