strat9_kernel/hardware/
thermal.rs1#![allow(dead_code)]
20
21use crate::hardware::ec;
22use core::sync::atomic::{AtomicBool, AtomicU16, AtomicU8, Ordering};
23use spin::Mutex;
24
25const EC_REG_TEMP_CPU: u8 = 0x78;
27
28const EC_REG_TEMP_GPU: u8 = 0x79;
30
31const EC_REG_TEMP_BAT: u8 = 0x7A;
33
34const EC_REG_FAN_CTRL: u8 = 0x93;
38
39const EC_REG_FAN_SPEED: u8 = 0x94;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45#[repr(u8)]
46pub enum FanSpeed {
47 Max = 0,
49 High = 2,
51 Medium = 4,
53 Low = 6,
55 Off = 7,
57}
58
59#[derive(Clone, Copy, Debug, Default)]
61pub struct ThermalState {
62 pub cpu_temp_c: u8,
63 pub gpu_temp_c: u8,
64 pub bat_temp_c: u8,
65 pub fan_rpm: u16,
66 pub fan_enabled: bool,
67 pub fan_level: u8,
68}
69
70static THERMAL_STATE: Mutex<ThermalState> = Mutex::new(ThermalState {
72 cpu_temp_c: 0,
73 gpu_temp_c: 0,
74 bat_temp_c: 0,
75 fan_rpm: 0,
76 fan_enabled: false,
77 fan_level: 0,
78});
79
80static THERMAL_INITIALIZED: AtomicBool = AtomicBool::new(false);
82
83static LAST_CPU_TEMP: AtomicU8 = AtomicU8::new(0);
85
86static LAST_FAN_RPM: AtomicU16 = AtomicU16::new(0);
88
89const FAN_ON_THRESHOLD: u8 = 55;
91const FAN_MAX_THRESHOLD: u8 = 85;
92const FAN_OFF_THRESHOLD: u8 = 45;
93
94static AUTO_FAN_CONTROL: AtomicBool = AtomicBool::new(true);
96
97pub fn init() {
99 if !ec::is_available() {
100 log::warn!("[Thermal] EC not available, thermal management disabled");
101 return;
102 }
103
104 let state = read_thermal_state();
105 if state.cpu_temp_c > 0 && state.cpu_temp_c < 130 {
106 THERMAL_INITIALIZED.store(true, Ordering::Release);
107 LAST_CPU_TEMP.store(state.cpu_temp_c, Ordering::Relaxed);
108 LAST_FAN_RPM.store(state.fan_rpm, Ordering::Relaxed);
109
110 log::info!(
111 "[Thermal] CPU={}°C GPU={}°C BAT={}°C Fan={}RPM (enabled={})",
112 state.cpu_temp_c,
113 state.gpu_temp_c,
114 state.bat_temp_c,
115 state.fan_rpm,
116 state.fan_enabled,
117 );
118 } else {
119 log::warn!(
120 "[Thermal] Failed to read temperature (got {}°C), EC may not be ThinkPad-compatible",
121 state.cpu_temp_c
122 );
123 }
124}
125
126pub fn read_thermal_state() -> ThermalState {
128 let cpu_temp = ec::read(EC_REG_TEMP_CPU).unwrap_or(0);
129 let gpu_temp = ec::read(EC_REG_TEMP_GPU).unwrap_or(0);
130 let bat_temp = ec::read(EC_REG_TEMP_BAT).unwrap_or(0);
131
132 let fan_lo = ec::read(EC_REG_FAN_SPEED).unwrap_or(0);
133 let fan_hi = ec::read(EC_REG_FAN_SPEED.wrapping_add(1)).unwrap_or(0);
134 let fan_rpm = u16::from_le_bytes([fan_lo, fan_hi]);
135
136 let fan_ctrl = ec::read(EC_REG_FAN_CTRL).unwrap_or(0x87);
137 let fan_enabled = (fan_ctrl & 1) == 0;
138 let fan_level = (fan_ctrl >> 1) & 0x7F;
139
140 ThermalState {
141 cpu_temp_c: cpu_temp,
142 gpu_temp_c: gpu_temp,
143 bat_temp_c: bat_temp,
144 fan_rpm,
145 fan_enabled,
146 fan_level,
147 }
148}
149
150pub fn poll() {
152 if !THERMAL_INITIALIZED.load(Ordering::Acquire) {
153 return;
154 }
155
156 let state = read_thermal_state();
157 LAST_CPU_TEMP.store(state.cpu_temp_c, Ordering::Relaxed);
158 LAST_FAN_RPM.store(state.fan_rpm, Ordering::Relaxed);
159
160 let mut current = THERMAL_STATE.lock();
161 *current = state;
162
163 if AUTO_FAN_CONTROL.load(Ordering::Relaxed) {
164 drop(current);
165 auto_fan_control(state.cpu_temp_c);
166 }
167}
168
169pub fn cpu_temperature() -> u8 {
171 LAST_CPU_TEMP.load(Ordering::Relaxed)
172}
173
174pub fn fan_rpm() -> u16 {
176 LAST_FAN_RPM.load(Ordering::Relaxed)
177}
178
179pub fn set_auto_fan_control(enabled: bool) {
181 AUTO_FAN_CONTROL.store(enabled, Ordering::Relaxed);
182}
183
184pub fn set_fan_speed(speed: FanSpeed) {
191 let ctrl: u8 = ((speed as u8) << 1) | 0; ec::write(EC_REG_FAN_CTRL, ctrl);
193}
194
195pub fn set_fan_enabled(enabled: bool) {
197 let current = ec::read(EC_REG_FAN_CTRL).unwrap_or(0x87);
198 let new_val = if enabled {
199 current & !1 } else {
201 current | 1 };
203 ec::write(EC_REG_FAN_CTRL, new_val);
204}
205
206fn auto_fan_control(cpu_temp: u8) {
208 if cpu_temp >= FAN_MAX_THRESHOLD {
209 set_fan_speed(FanSpeed::Max);
210 } else if cpu_temp >= FAN_ON_THRESHOLD {
211 set_fan_speed(FanSpeed::High);
212 } else if cpu_temp <= FAN_OFF_THRESHOLD {
213 set_fan_speed(FanSpeed::Off);
214 }
215 }
217
218pub fn dump_status() {
220 let state = read_thermal_state();
221 log::info!(
222 "[Thermal] CPU={}°C GPU={}°C BAT={}°C Fan={}RPM level={} auto={}",
223 state.cpu_temp_c,
224 state.gpu_temp_c,
225 state.bat_temp_c,
226 state.fan_rpm,
227 state.fan_level,
228 AUTO_FAN_CONTROL.load(Ordering::Relaxed),
229 );
230}