root/drivers/macintosh/windfarm_pm72.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Windfarm PowerMac thermal control.
 * Control loops for PowerMac7,2 and 7,3
 *
 * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp.
 */
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>

#include <asm/smu.h>

#include "windfarm.h"
#include "windfarm_pid.h"
#include "windfarm_mpu.h"

#define VERSION "1.0"

#undef DEBUG
#undef LOTSA_DEBUG

#ifdef DEBUG
#define DBG(args...)    printk(args)
#else
#define DBG(args...)    do { } while(0)
#endif

#ifdef LOTSA_DEBUG
#define DBG_LOTS(args...)       printk(args)
#else
#define DBG_LOTS(args...)       do { } while(0)
#endif

/* define this to force CPU overtemp to 60 degree, useful for testing
 * the overtemp code
 */
#undef HACKED_OVERTEMP

/* We currently only handle 2 chips */
#define NR_CHIPS        2
#define NR_CPU_FANS     3 * NR_CHIPS

/* Controls and sensors */
static struct wf_sensor *sens_cpu_temp[NR_CHIPS];
static struct wf_sensor *sens_cpu_volts[NR_CHIPS];
static struct wf_sensor *sens_cpu_amps[NR_CHIPS];
static struct wf_sensor *backside_temp;
static struct wf_sensor *drives_temp;

static struct wf_control *cpu_front_fans[NR_CHIPS];
static struct wf_control *cpu_rear_fans[NR_CHIPS];
static struct wf_control *cpu_pumps[NR_CHIPS];
static struct wf_control *backside_fan;
static struct wf_control *drives_fan;
static struct wf_control *slots_fan;
static struct wf_control *cpufreq_clamp;

/* We keep a temperature history for average calculation of 180s */
#define CPU_TEMP_HIST_SIZE      180

/* Fixed speed for slot fan */
#define SLOTS_FAN_DEFAULT_PWM   40

/* Scale value for CPU intake fans */
#define CPU_INTAKE_SCALE        0x0000f852

/* PID loop state */
static const struct mpu_data *cpu_mpu_data[NR_CHIPS];
static struct wf_cpu_pid_state cpu_pid[NR_CHIPS];
static bool cpu_pid_combined;
static u32 cpu_thist[CPU_TEMP_HIST_SIZE];
static int cpu_thist_pt;
static s64 cpu_thist_total;
static s32 cpu_all_tmax = 100 << 16;
static struct wf_pid_state backside_pid;
static int backside_tick;
static struct wf_pid_state drives_pid;
static int drives_tick;

static int nr_chips;
static bool have_all_controls;
static bool have_all_sensors;
static bool started;

static int failure_state;
#define FAILURE_SENSOR          1
#define FAILURE_FAN             2
#define FAILURE_PERM            4
#define FAILURE_LOW_OVERTEMP    8
#define FAILURE_HIGH_OVERTEMP   16

/* Overtemp values */
#define LOW_OVER_AVERAGE        0
#define LOW_OVER_IMMEDIATE      (10 << 16)
#define LOW_OVER_CLEAR          ((-10) << 16)
#define HIGH_OVER_IMMEDIATE     (14 << 16)
#define HIGH_OVER_AVERAGE       (10 << 16)
#define HIGH_OVER_IMMEDIATE     (14 << 16)


static void cpu_max_all_fans(void)
{
        int i;

        /* We max all CPU fans in case of a sensor error. We also do the
         * cpufreq clamping now, even if it's supposedly done later by the
         * generic code anyway, we do it earlier here to react faster
         */
        if (cpufreq_clamp)
                wf_control_set_max(cpufreq_clamp);
        for (i = 0; i < nr_chips; i++) {
                if (cpu_front_fans[i])
                        wf_control_set_max(cpu_front_fans[i]);
                if (cpu_rear_fans[i])
                        wf_control_set_max(cpu_rear_fans[i]);
                if (cpu_pumps[i])
                        wf_control_set_max(cpu_pumps[i]);
        }
}

static int cpu_check_overtemp(s32 temp)
{
        int new_state = 0;
        s32 t_avg, t_old;
        static bool first = true;

        /* First check for immediate overtemps */
        if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) {
                new_state |= FAILURE_LOW_OVERTEMP;
                if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
                        printk(KERN_ERR "windfarm: Overtemp due to immediate CPU"
                               " temperature !\n");
        }
        if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) {
                new_state |= FAILURE_HIGH_OVERTEMP;
                if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
                        printk(KERN_ERR "windfarm: Critical overtemp due to"
                               " immediate CPU temperature !\n");
        }

        /*
         * The first time around, initialize the array with the first
         * temperature reading
         */
        if (first) {
                int i;

                cpu_thist_total = 0;
                for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) {
                        cpu_thist[i] = temp;
                        cpu_thist_total += temp;
                }
                first = false;
        }

        /*
         * We calculate a history of max temperatures and use that for the
         * overtemp management
         */
        t_old = cpu_thist[cpu_thist_pt];
        cpu_thist[cpu_thist_pt] = temp;
        cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE;
        cpu_thist_total -= t_old;
        cpu_thist_total += temp;
        t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE;

        DBG_LOTS("  t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n",
                 FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp));

        /* Now check for average overtemps */
        if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) {
                new_state |= FAILURE_LOW_OVERTEMP;
                if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
                        printk(KERN_ERR "windfarm: Overtemp due to average CPU"
                               " temperature !\n");
        }
        if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) {
                new_state |= FAILURE_HIGH_OVERTEMP;
                if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
                        printk(KERN_ERR "windfarm: Critical overtemp due to"
                               " average CPU temperature !\n");
        }

        /* Now handle overtemp conditions. We don't currently use the windfarm
         * overtemp handling core as it's not fully suited to the needs of those
         * new machine. This will be fixed later.
         */
        if (new_state) {
                /* High overtemp -> immediate shutdown */
                if (new_state & FAILURE_HIGH_OVERTEMP)
                        machine_power_off();
                if ((failure_state & new_state) != new_state)
                        cpu_max_all_fans();
                failure_state |= new_state;
        } else if ((failure_state & FAILURE_LOW_OVERTEMP) &&
                   (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) {
                printk(KERN_ERR "windfarm: Overtemp condition cleared !\n");
                failure_state &= ~FAILURE_LOW_OVERTEMP;
        }

        return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP);
}

static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power)
{
        s32 dtemp, volts, amps;
        int rc;

        /* Get diode temperature */
        rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp);
        if (rc) {
                DBG("  CPU%d: temp reading error !\n", cpu);
                return -EIO;
        }
        DBG_LOTS("  CPU%d: temp   = %d.%03d\n", cpu, FIX32TOPRINT((dtemp)));
        *temp = dtemp;

        /* Get voltage */
        rc = wf_sensor_get(sens_cpu_volts[cpu], &volts);
        if (rc) {
                DBG("  CPU%d, volts reading error !\n", cpu);
                return -EIO;
        }
        DBG_LOTS("  CPU%d: volts  = %d.%03d\n", cpu, FIX32TOPRINT((volts)));

        /* Get current */
        rc = wf_sensor_get(sens_cpu_amps[cpu], &amps);
        if (rc) {
                DBG("  CPU%d, current reading error !\n", cpu);
                return -EIO;
        }
        DBG_LOTS("  CPU%d: amps   = %d.%03d\n", cpu, FIX32TOPRINT((amps)));

        /* Calculate power */

        /* Scale voltage and current raw sensor values according to fixed scales
         * obtained in Darwin and calculate power from I and V
         */
        *power = (((u64)volts) * ((u64)amps)) >> 16;

        DBG_LOTS("  CPU%d: power  = %d.%03d\n", cpu, FIX32TOPRINT((*power)));

        return 0;

}

static void cpu_fans_tick_split(void)
{
        int err, cpu;
        s32 intake, temp, power, t_max = 0;

        DBG_LOTS("* cpu fans_tick_split()\n");

        for (cpu = 0; cpu < nr_chips; ++cpu) {
                struct wf_cpu_pid_state *sp = &cpu_pid[cpu];

                /* Read current speed */
                wf_control_get(cpu_rear_fans[cpu], &sp->target);

                DBG_LOTS("  CPU%d: cur_target = %d RPM\n", cpu, sp->target);

                err = read_one_cpu_vals(cpu, &temp, &power);
                if (err) {
                        failure_state |= FAILURE_SENSOR;
                        cpu_max_all_fans();
                        return;
                }

                /* Keep track of highest temp */
                t_max = max(t_max, temp);

                /* Handle possible overtemps */
                if (cpu_check_overtemp(t_max))
                        return;

                /* Run PID */
                wf_cpu_pid_run(sp, power, temp);

                DBG_LOTS("  CPU%d: target = %d RPM\n", cpu, sp->target);

                /* Apply result directly to exhaust fan */
                err = wf_control_set(cpu_rear_fans[cpu], sp->target);
                if (err) {
                        pr_warn("wf_pm72: Fan %s reports error %d\n",
                                cpu_rear_fans[cpu]->name, err);
                        failure_state |= FAILURE_FAN;
                        break;
                }

                /* Scale result for intake fan */
                intake = (sp->target * CPU_INTAKE_SCALE) >> 16;
                DBG_LOTS("  CPU%d: intake = %d RPM\n", cpu, intake);
                err = wf_control_set(cpu_front_fans[cpu], intake);
                if (err) {
                        pr_warn("wf_pm72: Fan %s reports error %d\n",
                                cpu_front_fans[cpu]->name, err);
                        failure_state |= FAILURE_FAN;
                        break;
                }
        }
}

static void cpu_fans_tick_combined(void)
{
        s32 temp0, power0, temp1, power1, t_max = 0;
        s32 temp, power, intake, pump;
        struct wf_control *pump0, *pump1;
        struct wf_cpu_pid_state *sp = &cpu_pid[0];
        int err, cpu;

        DBG_LOTS("* cpu fans_tick_combined()\n");

        /* Read current speed from cpu 0 */
        wf_control_get(cpu_rear_fans[0], &sp->target);

        DBG_LOTS("  CPUs: cur_target = %d RPM\n", sp->target);

        /* Read values for both CPUs */
        err = read_one_cpu_vals(0, &temp0, &power0);
        if (err) {
                failure_state |= FAILURE_SENSOR;
                cpu_max_all_fans();
                return;
        }
        err = read_one_cpu_vals(1, &temp1, &power1);
        if (err) {
                failure_state |= FAILURE_SENSOR;
                cpu_max_all_fans();
                return;
        }

        /* Keep track of highest temp */
        t_max = max(t_max, max(temp0, temp1));

        /* Handle possible overtemps */
        if (cpu_check_overtemp(t_max))
                return;

        /* Use the max temp & power of both */
        temp = max(temp0, temp1);
        power = max(power0, power1);

        /* Run PID */
        wf_cpu_pid_run(sp, power, temp);

        /* Scale result for intake fan */
        intake = (sp->target * CPU_INTAKE_SCALE) >> 16;

        /* Same deal with pump speed */
        pump0 = cpu_pumps[0];
        pump1 = cpu_pumps[1];
        if (!pump0) {
                pump0 = pump1;
                pump1 = NULL;
        }
        pump = (sp->target * wf_control_get_max(pump0)) /
                cpu_mpu_data[0]->rmaxn_exhaust_fan;

        DBG_LOTS("  CPUs: target = %d RPM\n", sp->target);
        DBG_LOTS("  CPUs: intake = %d RPM\n", intake);
        DBG_LOTS("  CPUs: pump   = %d RPM\n", pump);

        for (cpu = 0; cpu < nr_chips; cpu++) {
                err = wf_control_set(cpu_rear_fans[cpu], sp->target);
                if (err) {
                        pr_warn("wf_pm72: Fan %s reports error %d\n",
                                cpu_rear_fans[cpu]->name, err);
                        failure_state |= FAILURE_FAN;
                }
                err = wf_control_set(cpu_front_fans[cpu], intake);
                if (err) {
                        pr_warn("wf_pm72: Fan %s reports error %d\n",
                                cpu_front_fans[cpu]->name, err);
                        failure_state |= FAILURE_FAN;
                }
                err = 0;
                if (cpu_pumps[cpu])
                        err = wf_control_set(cpu_pumps[cpu], pump);
                if (err) {
                        pr_warn("wf_pm72: Pump %s reports error %d\n",
                                cpu_pumps[cpu]->name, err);
                        failure_state |= FAILURE_FAN;
                }
        }
}

/* Implementation... */
static int cpu_setup_pid(int cpu)
{
        struct wf_cpu_pid_param pid;
        const struct mpu_data *mpu = cpu_mpu_data[cpu];
        s32 tmax, ttarget, ptarget;
        int fmin, fmax, hsize;

        /* Get PID params from the appropriate MPU EEPROM */
        tmax = mpu->tmax << 16;
        ttarget = mpu->ttarget << 16;
        ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16;

        DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n",
            cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax));

        /* We keep a global tmax for overtemp calculations */
        if (tmax < cpu_all_tmax)
                cpu_all_tmax = tmax;

        /* Set PID min/max by using the rear fan min/max */
        fmin = wf_control_get_min(cpu_rear_fans[cpu]);
        fmax = wf_control_get_max(cpu_rear_fans[cpu]);
        DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax);

        /* History size */
        hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY);
        DBG("wf_72: CPU%d history size = %d\n", cpu, hsize);

        /* Initialize PID loop */
        pid.interval    = 1;    /* seconds */
        pid.history_len = hsize;
        pid.gd          = mpu->pid_gd;
        pid.gp          = mpu->pid_gp;
        pid.gr          = mpu->pid_gr;
        pid.tmax        = tmax;
        pid.ttarget     = ttarget;
        pid.pmaxadj     = ptarget;
        pid.min         = fmin;
        pid.max         = fmax;

        wf_cpu_pid_init(&cpu_pid[cpu], &pid);
        cpu_pid[cpu].target = 1000;

        return 0;
}

/* Backside/U3 fan */
static struct wf_pid_param backside_u3_param = {
        .interval       = 5,
        .history_len    = 2,
        .gd             = 40 << 20,
        .gp             = 5 << 20,
        .gr             = 0,
        .itarget        = 65 << 16,
        .additive       = 1,
        .min            = 20,
        .max            = 100,
};

static struct wf_pid_param backside_u3h_param = {
        .interval       = 5,
        .history_len    = 2,
        .gd             = 20 << 20,
        .gp             = 5 << 20,
        .gr             = 0,
        .itarget        = 75 << 16,
        .additive       = 1,
        .min            = 20,
        .max            = 100,
};

static void backside_fan_tick(void)
{
        s32 temp;
        int speed;
        int err;

        if (!backside_fan || !backside_temp || !backside_tick)
                return;
        if (--backside_tick > 0)
                return;
        backside_tick = backside_pid.param.interval;

        DBG_LOTS("* backside fans tick\n");

        /* Update fan speed from actual fans */
        err = wf_control_get(backside_fan, &speed);
        if (!err)
                backside_pid.target = speed;

        err = wf_sensor_get(backside_temp, &temp);
        if (err) {
                printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n",
                       err);
                failure_state |= FAILURE_SENSOR;
                wf_control_set_max(backside_fan);
                return;
        }
        speed = wf_pid_run(&backside_pid, temp);

        DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n",
                 FIX32TOPRINT(temp), speed);

        err = wf_control_set(backside_fan, speed);
        if (err) {
                printk(KERN_WARNING "windfarm: backside fan error %d\n", err);
                failure_state |= FAILURE_FAN;
        }
}

static void backside_setup_pid(void)
{
        /* first time initialize things */
        s32 fmin = wf_control_get_min(backside_fan);
        s32 fmax = wf_control_get_max(backside_fan);
        struct wf_pid_param param;
        struct device_node *u3;
        int u3h = 1; /* conservative by default */

        u3 = of_find_node_by_path("/u3@0,f8000000");
        if (u3 != NULL) {
                const u32 *vers = of_get_property(u3, "device-rev", NULL);
                if (vers)
                        if (((*vers) & 0x3f) < 0x34)
                                u3h = 0;
                of_node_put(u3);
        }

        param = u3h ? backside_u3h_param : backside_u3_param;

        param.min = max(param.min, fmin);
        param.max = min(param.max, fmax);
        wf_pid_init(&backside_pid, &param);
        backside_tick = 1;

        pr_info("wf_pm72: Backside control loop started.\n");
}

/* Drive bay fan */
static const struct wf_pid_param drives_param = {
        .interval       = 5,
        .history_len    = 2,
        .gd             = 30 << 20,
        .gp             = 5 << 20,
        .gr             = 0,
        .itarget        = 40 << 16,
        .additive       = 1,
        .min            = 300,
        .max            = 4000,
};

static void drives_fan_tick(void)
{
        s32 temp;
        int speed;
        int err;

        if (!drives_fan || !drives_temp || !drives_tick)
                return;
        if (--drives_tick > 0)
                return;
        drives_tick = drives_pid.param.interval;

        DBG_LOTS("* drives fans tick\n");

        /* Update fan speed from actual fans */
        err = wf_control_get(drives_fan, &speed);
        if (!err)
                drives_pid.target = speed;

        err = wf_sensor_get(drives_temp, &temp);
        if (err) {
                pr_warn("wf_pm72: drive bay temp sensor error %d\n", err);
                failure_state |= FAILURE_SENSOR;
                wf_control_set_max(drives_fan);
                return;
        }
        speed = wf_pid_run(&drives_pid, temp);

        DBG_LOTS("drives PID temp=%d.%.3d speed=%d\n",
                 FIX32TOPRINT(temp), speed);

        err = wf_control_set(drives_fan, speed);
        if (err) {
                printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err);
                failure_state |= FAILURE_FAN;
        }
}

static void drives_setup_pid(void)
{
        /* first time initialize things */
        s32 fmin = wf_control_get_min(drives_fan);
        s32 fmax = wf_control_get_max(drives_fan);
        struct wf_pid_param param = drives_param;

        param.min = max(param.min, fmin);
        param.max = min(param.max, fmax);
        wf_pid_init(&drives_pid, &param);
        drives_tick = 1;

        pr_info("wf_pm72: Drive bay control loop started.\n");
}

static void set_fail_state(void)
{
        cpu_max_all_fans();

        if (backside_fan)
                wf_control_set_max(backside_fan);
        if (slots_fan)
                wf_control_set_max(slots_fan);
        if (drives_fan)
                wf_control_set_max(drives_fan);
}

static void pm72_tick(void)
{
        int i, last_failure;

        if (!started) {
                started = true;
                printk(KERN_INFO "windfarm: CPUs control loops started.\n");
                for (i = 0; i < nr_chips; ++i) {
                        if (cpu_setup_pid(i) < 0) {
                                failure_state = FAILURE_PERM;
                                set_fail_state();
                                break;
                        }
                }
                DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax));

                backside_setup_pid();
                drives_setup_pid();

                /*
                 * We don't have the right stuff to drive the PCI fan
                 * so we fix it to a default value
                 */
                wf_control_set(slots_fan, SLOTS_FAN_DEFAULT_PWM);

#ifdef HACKED_OVERTEMP
                cpu_all_tmax = 60 << 16;
#endif
        }

        /* Permanent failure, bail out */
        if (failure_state & FAILURE_PERM)
                return;

        /*
         * Clear all failure bits except low overtemp which will be eventually
         * cleared by the control loop itself
         */
        last_failure = failure_state;
        failure_state &= FAILURE_LOW_OVERTEMP;
        if (cpu_pid_combined)
                cpu_fans_tick_combined();
        else
                cpu_fans_tick_split();
        backside_fan_tick();
        drives_fan_tick();

        DBG_LOTS("  last_failure: 0x%x, failure_state: %x\n",
                 last_failure, failure_state);

        /* Check for failures. Any failure causes cpufreq clamping */
        if (failure_state && last_failure == 0 && cpufreq_clamp)
                wf_control_set_max(cpufreq_clamp);
        if (failure_state == 0 && last_failure && cpufreq_clamp)
                wf_control_set_min(cpufreq_clamp);

        /* That's it for now, we might want to deal with other failures
         * differently in the future though
         */
}

static void pm72_new_control(struct wf_control *ct)
{
        bool all_controls;
        bool had_pump = cpu_pumps[0] || cpu_pumps[1];

        if (!strcmp(ct->name, "cpu-front-fan-0"))
                cpu_front_fans[0] = ct;
        else if (!strcmp(ct->name, "cpu-front-fan-1"))
                cpu_front_fans[1] = ct;
        else if (!strcmp(ct->name, "cpu-rear-fan-0"))
                cpu_rear_fans[0] = ct;
        else if (!strcmp(ct->name, "cpu-rear-fan-1"))
                cpu_rear_fans[1] = ct;
        else if (!strcmp(ct->name, "cpu-pump-0"))
                cpu_pumps[0] = ct;
        else if (!strcmp(ct->name, "cpu-pump-1"))
                cpu_pumps[1] = ct;
        else if (!strcmp(ct->name, "backside-fan"))
                backside_fan = ct;
        else if (!strcmp(ct->name, "slots-fan"))
                slots_fan = ct;
        else if (!strcmp(ct->name, "drive-bay-fan"))
                drives_fan = ct;
        else if (!strcmp(ct->name, "cpufreq-clamp"))
                cpufreq_clamp = ct;

        all_controls =
                cpu_front_fans[0] &&
                cpu_rear_fans[0] &&
                backside_fan &&
                slots_fan &&
                drives_fan;
        if (nr_chips > 1)
                all_controls &=
                        cpu_front_fans[1] &&
                        cpu_rear_fans[1];
        have_all_controls = all_controls;

        if ((cpu_pumps[0] || cpu_pumps[1]) && !had_pump) {
                pr_info("wf_pm72: Liquid cooling pump(s) detected,"
                        " using new algorithm !\n");
                cpu_pid_combined = true;
        }
}


static void pm72_new_sensor(struct wf_sensor *sr)
{
        bool all_sensors;

        if (!strcmp(sr->name, "cpu-diode-temp-0"))
                sens_cpu_temp[0] = sr;
        else if (!strcmp(sr->name, "cpu-diode-temp-1"))
                sens_cpu_temp[1] = sr;
        else if (!strcmp(sr->name, "cpu-voltage-0"))
                sens_cpu_volts[0] = sr;
        else if (!strcmp(sr->name, "cpu-voltage-1"))
                sens_cpu_volts[1] = sr;
        else if (!strcmp(sr->name, "cpu-current-0"))
                sens_cpu_amps[0] = sr;
        else if (!strcmp(sr->name, "cpu-current-1"))
                sens_cpu_amps[1] = sr;
        else if (!strcmp(sr->name, "backside-temp"))
                backside_temp = sr;
        else if (!strcmp(sr->name, "hd-temp"))
                drives_temp = sr;

        all_sensors =
                sens_cpu_temp[0] &&
                sens_cpu_volts[0] &&
                sens_cpu_amps[0] &&
                backside_temp &&
                drives_temp;
        if (nr_chips > 1)
                all_sensors &=
                        sens_cpu_temp[1] &&
                        sens_cpu_volts[1] &&
                        sens_cpu_amps[1];

        have_all_sensors = all_sensors;
}

static int pm72_wf_notify(struct notifier_block *self,
                          unsigned long event, void *data)
{
        switch (event) {
        case WF_EVENT_NEW_SENSOR:
                pm72_new_sensor(data);
                break;
        case WF_EVENT_NEW_CONTROL:
                pm72_new_control(data);
                break;
        case WF_EVENT_TICK:
                if (have_all_controls && have_all_sensors)
                        pm72_tick();
        }
        return 0;
}

static struct notifier_block pm72_events = {
        .notifier_call = pm72_wf_notify,
};

static int wf_pm72_probe(struct platform_device *dev)
{
        wf_register_client(&pm72_events);
        return 0;
}

static void wf_pm72_remove(struct platform_device *dev)
{
        wf_unregister_client(&pm72_events);
}

static struct platform_driver wf_pm72_driver = {
        .probe  = wf_pm72_probe,
        .remove = wf_pm72_remove,
        .driver = {
                .name = "windfarm",
        },
};

static int __init wf_pm72_init(void)
{
        struct device_node *cpu;
        int i;

        if (!of_machine_is_compatible("PowerMac7,2") &&
            !of_machine_is_compatible("PowerMac7,3"))
                return -ENODEV;

        /* Count the number of CPU cores */
        nr_chips = 0;
        for_each_node_by_type(cpu, "cpu")
                ++nr_chips;
        if (nr_chips > NR_CHIPS)
                nr_chips = NR_CHIPS;

        pr_info("windfarm: Initializing for desktop G5 with %d chips\n",
                nr_chips);

        /* Get MPU data for each CPU */
        for (i = 0; i < nr_chips; i++) {
                cpu_mpu_data[i] = wf_get_mpu(i);
                if (!cpu_mpu_data[i]) {
                        pr_err("wf_pm72: Failed to find MPU data for CPU %d\n", i);
                        return -ENXIO;
                }
        }

#ifdef MODULE
        request_module("windfarm_fcu_controls");
        request_module("windfarm_lm75_sensor");
        request_module("windfarm_ad7417_sensor");
        request_module("windfarm_max6690_sensor");
        request_module("windfarm_cpufreq_clamp");
#endif /* MODULE */

        platform_driver_register(&wf_pm72_driver);
        return 0;
}

static void __exit wf_pm72_exit(void)
{
        platform_driver_unregister(&wf_pm72_driver);
}

module_init(wf_pm72_init);
module_exit(wf_pm72_exit);

MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Thermal control for AGP PowerMac G5s");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:windfarm");