#include "smc.h"
#include <sys/kthread.h>
#define THERMAL_MIN_INTERVAL 1
#define THERMAL_MAX_INTERVAL 60
#define THERMAL_MIN_TEMP 10000
#define THERMAL_RAMP_DOWN_MAX 200
#define THERMAL_WRITE_DELTA 100
int
apple_smc_thermal_fans_to_max(device_t dev)
{
struct apple_smc_softc *sc = device_get_softc(dev);
int i, max_rpm, ok = 0;
if (apple_smc_fan_reassert_manual(dev) != 0)
device_printf(dev,
"thermal: WARNING: manual mode reassert failed\n");
for (i = 0; i < sc->sc_nfan; i++) {
max_rpm = apple_smc_fan_getvalue(dev, ASMC_KEY_FANMAXSPEED, i);
if (max_rpm <= 0)
continue;
if (apple_smc_fan_setvalue(dev, ASMC_KEY_FANTARGETSPEED,
i, max_rpm) == 0)
ok++;
}
return (ok);
}
static int
apple_smc_thermal_read_cpu_temp(device_t dev)
{
struct apple_smc_softc *sc = device_get_softc(dev);
int val, best, i, error;
best = -1;
error = apple_smc_sensor_read(dev, "TC0c", &val);
if (error == 0 && val > THERMAL_MIN_TEMP && val < 130000)
best = val;
for (i = 0; i < sc->sc_temp_count; i++) {
if (sc->sc_temp_sensors[i][0] != 'T' ||
sc->sc_temp_sensors[i][1] != 'C')
continue;
error = apple_smc_sensor_read(dev,
sc->sc_temp_sensors[i], &val);
if (error == 0 && val > best && val > THERMAL_MIN_TEMP &&
val < 130000)
best = val;
}
return (best);
}
static int
apple_smc_thermal_compute_rpm(int temp_mc, int temp_low_mc, int temp_high_mc,
int min_rpm, int max_rpm)
{
int64_t range, delta, result;
if (temp_high_mc <= temp_low_mc)
return (max_rpm);
if (temp_mc <= temp_low_mc)
return (min_rpm);
if (temp_mc >= temp_high_mc)
return (max_rpm);
range = (int64_t)(temp_high_mc - temp_low_mc);
delta = (int64_t)(temp_mc - temp_low_mc);
result = (int64_t)min_rpm +
((int64_t)(max_rpm - min_rpm) * delta) / range;
if (result < min_rpm)
return (min_rpm);
if (result > max_rpm)
return (max_rpm);
return ((int)result);
}
static void
apple_smc_thermal_thread(void *arg)
{
device_t dev = (device_t)arg;
struct apple_smc_softc *sc = device_get_softc(dev);
int temp_mc, target_rpm, cur_target, i;
int min_rpm, max_rpm;
int last_temp = 0;
int sensor_fail_logged = 0;
int interval;
int s_temp_low, s_temp_high, s_hysteresis, s_emergency;
while (!sc->sc_thermal_stop) {
interval = sc->sc_thermal_interval;
if (interval < THERMAL_MIN_INTERVAL)
interval = THERMAL_MIN_INTERVAL;
if (interval > THERMAL_MAX_INTERVAL)
interval = THERMAL_MAX_INTERVAL;
if (!sc->sc_thermal_enabled) {
tsleep(&sc->sc_thermal_enabled, 0, "smcthw",
hz * interval);
continue;
}
s_temp_low = sc->sc_thermal_temp_low;
s_temp_high = sc->sc_thermal_temp_high;
s_hysteresis = sc->sc_thermal_hysteresis;
s_emergency = sc->sc_thermal_emergency;
temp_mc = apple_smc_thermal_read_cpu_temp(dev);
if (temp_mc < 0) {
apple_smc_thermal_fans_to_max(dev);
if (!sensor_fail_logged) {
device_printf(dev,
"thermal: sensor read failed, "
"fans to max\n");
sensor_fail_logged = 1;
}
tsleep(&sc->sc_thermal_stop, 0, "smcthf",
hz * interval);
continue;
}
sensor_fail_logged = 0;
if (temp_mc >= s_emergency) {
if (last_temp < s_emergency)
device_printf(dev,
"thermal: EMERGENCY %d.%03dC, "
"fans to max\n",
temp_mc / 1000, temp_mc % 1000);
apple_smc_thermal_fans_to_max(dev);
last_temp = temp_mc;
tsleep(&sc->sc_thermal_stop, 0, "smcthe",
hz * interval);
continue;
}
apple_smc_fan_reassert_manual(dev);
for (i = 0; i < sc->sc_nfan; i++) {
if (!(sc->sc_fan_manual_mask & (1 << i)))
continue;
min_rpm = apple_smc_fan_getvalue(dev,
ASMC_KEY_FANMINSPEED, i);
max_rpm = apple_smc_fan_getvalue(dev,
ASMC_KEY_FANMAXSPEED, i);
if (min_rpm <= 0 || max_rpm <= 0 || max_rpm < min_rpm)
continue;
target_rpm = apple_smc_thermal_compute_rpm(
temp_mc, s_temp_low, s_temp_high,
min_rpm, max_rpm);
cur_target = apple_smc_fan_getvalue(dev,
ASMC_KEY_FANTARGETSPEED, i);
if (cur_target > 0) {
int delta = target_rpm - cur_target;
if (abs(delta) < THERMAL_WRITE_DELTA &&
abs(temp_mc - last_temp) < s_hysteresis)
continue;
if (delta < -THERMAL_RAMP_DOWN_MAX)
target_rpm =
cur_target - THERMAL_RAMP_DOWN_MAX;
}
apple_smc_fan_setvalue(dev,
ASMC_KEY_FANTARGETSPEED, i, target_rpm);
}
last_temp = temp_mc;
tsleep(&sc->sc_thermal_stop, 0, "smcth",
hz * interval);
}
sc->sc_thermal_running = 0;
wakeup(&sc->sc_thermal_running);
}
int
apple_smc_thermal_start(device_t dev)
{
struct apple_smc_softc *sc = device_get_softc(dev);
int error;
sc->sc_thermal_stop = 0;
sc->sc_thermal_running = 0;
error = kthread_create(apple_smc_thermal_thread, dev,
&sc->sc_thermal_td, "apple_smc_thermal");
if (error == 0)
sc->sc_thermal_running = 1;
return (error);
}
void
apple_smc_thermal_stop(device_t dev)
{
struct apple_smc_softc *sc = device_get_softc(dev);
if (!sc->sc_thermal_running)
return;
sc->sc_thermal_stop = 1;
wakeup(&sc->sc_thermal_enabled);
wakeup(&sc->sc_thermal_stop);
while (sc->sc_thermal_running)
tsleep(&sc->sc_thermal_running, 0, "smcthx", hz);
sc->sc_thermal_td = NULL;
}
int
apple_smc_thermal_enabled_sysctl(SYSCTL_HANDLER_ARGS)
{
device_t dev = (device_t)arg1;
struct apple_smc_softc *sc = device_get_softc(dev);
int val, error;
val = sc->sc_thermal_enabled;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error || req->newptr == NULL)
return (error);
val = (val != 0) ? 1 : 0;
if (val == sc->sc_thermal_enabled)
return (0);
sc->sc_thermal_enabled = val;
if (!val && sc->sc_prochot_override) {
apple_smc_thermal_fans_to_max(dev);
device_printf(dev,
"thermal: governor disabled, fans set to max\n");
}
wakeup(&sc->sc_thermal_enabled);
return (0);
}
int
apple_smc_thermal_int_sysctl(SYSCTL_HANDLER_ARGS)
{
int *valp = (int *)arg1;
int val, error;
val = *valp;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error || req->newptr == NULL)
return (error);
if (val <= 0)
return (EINVAL);
*valp = val;
return (0);
}