162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Windfarm PowerMac thermal control. FCU fan control
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2012 Benjamin Herrenschmidt, IBM Corp.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#undef DEBUG
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/types.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/delay.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/init.h>
1562306a36Sopenharmony_ci#include <linux/wait.h>
1662306a36Sopenharmony_ci#include <linux/i2c.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <asm/machdep.h>
1962306a36Sopenharmony_ci#include <asm/io.h>
2062306a36Sopenharmony_ci#include <asm/sections.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include "windfarm.h"
2362306a36Sopenharmony_ci#include "windfarm_mpu.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define VERSION "1.0"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#ifdef DEBUG
2862306a36Sopenharmony_ci#define DBG(args...)	printk(args)
2962306a36Sopenharmony_ci#else
3062306a36Sopenharmony_ci#define DBG(args...)	do { } while(0)
3162306a36Sopenharmony_ci#endif
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/*
3462306a36Sopenharmony_ci * This option is "weird" :) Basically, if you define this to 1
3562306a36Sopenharmony_ci * the control loop for the RPMs fans (not PWMs) will apply the
3662306a36Sopenharmony_ci * correction factor obtained from the PID to the actual RPM
3762306a36Sopenharmony_ci * speed read from the FCU.
3862306a36Sopenharmony_ci *
3962306a36Sopenharmony_ci * If you define the below constant to 0, then it will be
4062306a36Sopenharmony_ci * applied to the setpoint RPM speed, that is basically the
4162306a36Sopenharmony_ci * speed we proviously "asked" for.
4262306a36Sopenharmony_ci *
4362306a36Sopenharmony_ci * I'm using 0 for now which is what therm_pm72 used to do and
4462306a36Sopenharmony_ci * what Darwin -apparently- does based on observed behaviour.
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_ci#define RPM_PID_USE_ACTUAL_SPEED	0
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* Default min/max for pumps */
4962306a36Sopenharmony_ci#define CPU_PUMP_OUTPUT_MAX		3200
5062306a36Sopenharmony_ci#define CPU_PUMP_OUTPUT_MIN		1250
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define FCU_FAN_RPM		0
5362306a36Sopenharmony_ci#define FCU_FAN_PWM		1
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistruct wf_fcu_priv {
5662306a36Sopenharmony_ci	struct kref		ref;
5762306a36Sopenharmony_ci	struct i2c_client	*i2c;
5862306a36Sopenharmony_ci	struct mutex		lock;
5962306a36Sopenharmony_ci	struct list_head	fan_list;
6062306a36Sopenharmony_ci	int			rpm_shift;
6162306a36Sopenharmony_ci};
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistruct wf_fcu_fan {
6462306a36Sopenharmony_ci	struct list_head	link;
6562306a36Sopenharmony_ci	int			id;
6662306a36Sopenharmony_ci	s32			min, max, target;
6762306a36Sopenharmony_ci	struct wf_fcu_priv	*fcu_priv;
6862306a36Sopenharmony_ci	struct wf_control	ctrl;
6962306a36Sopenharmony_ci};
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic void wf_fcu_release(struct kref *ref)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	kfree(pv);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic void wf_fcu_fan_release(struct wf_control *ct)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	struct wf_fcu_fan *fan = ct->priv;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	kref_put(&fan->fcu_priv->ref, wf_fcu_release);
8362306a36Sopenharmony_ci	kfree(fan);
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg,
8762306a36Sopenharmony_ci			   unsigned char *buf, int nb)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	int tries, nr, nw;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	mutex_lock(&pv->lock);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	buf[0] = reg;
9462306a36Sopenharmony_ci	tries = 0;
9562306a36Sopenharmony_ci	for (;;) {
9662306a36Sopenharmony_ci		nw = i2c_master_send(pv->i2c, buf, 1);
9762306a36Sopenharmony_ci		if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100)
9862306a36Sopenharmony_ci			break;
9962306a36Sopenharmony_ci		msleep(10);
10062306a36Sopenharmony_ci		++tries;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci	if (nw <= 0) {
10362306a36Sopenharmony_ci		pr_err("Failure writing address to FCU: %d", nw);
10462306a36Sopenharmony_ci		nr = nw;
10562306a36Sopenharmony_ci		goto bail;
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci	tries = 0;
10862306a36Sopenharmony_ci	for (;;) {
10962306a36Sopenharmony_ci		nr = i2c_master_recv(pv->i2c, buf, nb);
11062306a36Sopenharmony_ci		if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100)
11162306a36Sopenharmony_ci			break;
11262306a36Sopenharmony_ci		msleep(10);
11362306a36Sopenharmony_ci		++tries;
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci	if (nr <= 0)
11662306a36Sopenharmony_ci		pr_err("wf_fcu: Failure reading data from FCU: %d", nw);
11762306a36Sopenharmony_ci bail:
11862306a36Sopenharmony_ci	mutex_unlock(&pv->lock);
11962306a36Sopenharmony_ci	return nr;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg,
12362306a36Sopenharmony_ci			    const unsigned char *ptr, int nb)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	int tries, nw;
12662306a36Sopenharmony_ci	unsigned char buf[16];
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	buf[0] = reg;
12962306a36Sopenharmony_ci	memcpy(buf+1, ptr, nb);
13062306a36Sopenharmony_ci	++nb;
13162306a36Sopenharmony_ci	tries = 0;
13262306a36Sopenharmony_ci	for (;;) {
13362306a36Sopenharmony_ci		nw = i2c_master_send(pv->i2c, buf, nb);
13462306a36Sopenharmony_ci		if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100)
13562306a36Sopenharmony_ci			break;
13662306a36Sopenharmony_ci		msleep(10);
13762306a36Sopenharmony_ci		++tries;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci	if (nw < 0)
14062306a36Sopenharmony_ci		pr_err("wf_fcu: Failure writing to FCU: %d", nw);
14162306a36Sopenharmony_ci	return nw;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct wf_fcu_fan *fan = ct->priv;
14762306a36Sopenharmony_ci	struct wf_fcu_priv *pv = fan->fcu_priv;
14862306a36Sopenharmony_ci	int rc, shift = pv->rpm_shift;
14962306a36Sopenharmony_ci	unsigned char buf[2];
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (value < fan->min)
15262306a36Sopenharmony_ci		value = fan->min;
15362306a36Sopenharmony_ci	if (value > fan->max)
15462306a36Sopenharmony_ci		value = fan->max;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	fan->target = value;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	buf[0] = value >> (8 - shift);
15962306a36Sopenharmony_ci	buf[1] = value << shift;
16062306a36Sopenharmony_ci	rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2);
16162306a36Sopenharmony_ci	if (rc < 0)
16262306a36Sopenharmony_ci		return -EIO;
16362306a36Sopenharmony_ci	return 0;
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	struct wf_fcu_fan *fan = ct->priv;
16962306a36Sopenharmony_ci	struct wf_fcu_priv *pv = fan->fcu_priv;
17062306a36Sopenharmony_ci	int rc, reg_base, shift = pv->rpm_shift;
17162306a36Sopenharmony_ci	unsigned char failure;
17262306a36Sopenharmony_ci	unsigned char active;
17362306a36Sopenharmony_ci	unsigned char buf[2];
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	rc = wf_fcu_read_reg(pv, 0xb, &failure, 1);
17662306a36Sopenharmony_ci	if (rc != 1)
17762306a36Sopenharmony_ci		return -EIO;
17862306a36Sopenharmony_ci	if ((failure & (1 << fan->id)) != 0)
17962306a36Sopenharmony_ci		return -EFAULT;
18062306a36Sopenharmony_ci	rc = wf_fcu_read_reg(pv, 0xd, &active, 1);
18162306a36Sopenharmony_ci	if (rc != 1)
18262306a36Sopenharmony_ci		return -EIO;
18362306a36Sopenharmony_ci	if ((active & (1 << fan->id)) == 0)
18462306a36Sopenharmony_ci		return -ENXIO;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	/* Programmed value or real current speed */
18762306a36Sopenharmony_ci#if RPM_PID_USE_ACTUAL_SPEED
18862306a36Sopenharmony_ci	reg_base = 0x11;
18962306a36Sopenharmony_ci#else
19062306a36Sopenharmony_ci	reg_base = 0x10;
19162306a36Sopenharmony_ci#endif
19262306a36Sopenharmony_ci	rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2);
19362306a36Sopenharmony_ci	if (rc != 2)
19462306a36Sopenharmony_ci		return -EIO;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	*value = (buf[0] << (8 - shift)) | buf[1] >> shift;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	return 0;
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	struct wf_fcu_fan *fan = ct->priv;
20462306a36Sopenharmony_ci	struct wf_fcu_priv *pv = fan->fcu_priv;
20562306a36Sopenharmony_ci	unsigned char buf[2];
20662306a36Sopenharmony_ci	int rc;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (value < fan->min)
20962306a36Sopenharmony_ci		value = fan->min;
21062306a36Sopenharmony_ci	if (value > fan->max)
21162306a36Sopenharmony_ci		value = fan->max;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	fan->target = value;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	value = (value * 2559) / 1000;
21662306a36Sopenharmony_ci	buf[0] = value;
21762306a36Sopenharmony_ci	rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1);
21862306a36Sopenharmony_ci	if (rc < 0)
21962306a36Sopenharmony_ci		return -EIO;
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct wf_fcu_fan *fan = ct->priv;
22662306a36Sopenharmony_ci	struct wf_fcu_priv *pv = fan->fcu_priv;
22762306a36Sopenharmony_ci	unsigned char failure;
22862306a36Sopenharmony_ci	unsigned char active;
22962306a36Sopenharmony_ci	unsigned char buf[2];
23062306a36Sopenharmony_ci	int rc;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1);
23362306a36Sopenharmony_ci	if (rc != 1)
23462306a36Sopenharmony_ci		return -EIO;
23562306a36Sopenharmony_ci	if ((failure & (1 << fan->id)) != 0)
23662306a36Sopenharmony_ci		return -EFAULT;
23762306a36Sopenharmony_ci	rc = wf_fcu_read_reg(pv, 0x2d, &active, 1);
23862306a36Sopenharmony_ci	if (rc != 1)
23962306a36Sopenharmony_ci		return -EIO;
24062306a36Sopenharmony_ci	if ((active & (1 << fan->id)) == 0)
24162306a36Sopenharmony_ci		return -ENXIO;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1);
24462306a36Sopenharmony_ci	if (rc != 1)
24562306a36Sopenharmony_ci		return -EIO;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	*value = (((s32)buf[0]) * 1000) / 2559;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	return 0;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic s32 wf_fcu_fan_min(struct wf_control *ct)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct wf_fcu_fan *fan = ct->priv;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	return fan->min;
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic s32 wf_fcu_fan_max(struct wf_control *ct)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	struct wf_fcu_fan *fan = ct->priv;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	return fan->max;
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cistatic const struct wf_control_ops wf_fcu_fan_rpm_ops = {
26762306a36Sopenharmony_ci	.set_value	= wf_fcu_fan_set_rpm,
26862306a36Sopenharmony_ci	.get_value	= wf_fcu_fan_get_rpm,
26962306a36Sopenharmony_ci	.get_min	= wf_fcu_fan_min,
27062306a36Sopenharmony_ci	.get_max	= wf_fcu_fan_max,
27162306a36Sopenharmony_ci	.release	= wf_fcu_fan_release,
27262306a36Sopenharmony_ci	.owner		= THIS_MODULE,
27362306a36Sopenharmony_ci};
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic const struct wf_control_ops wf_fcu_fan_pwm_ops = {
27662306a36Sopenharmony_ci	.set_value	= wf_fcu_fan_set_pwm,
27762306a36Sopenharmony_ci	.get_value	= wf_fcu_fan_get_pwm,
27862306a36Sopenharmony_ci	.get_min	= wf_fcu_fan_min,
27962306a36Sopenharmony_ci	.get_max	= wf_fcu_fan_max,
28062306a36Sopenharmony_ci	.release	= wf_fcu_fan_release,
28162306a36Sopenharmony_ci	.owner		= THIS_MODULE,
28262306a36Sopenharmony_ci};
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cistatic void wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	const struct mpu_data *mpu = wf_get_mpu(0);
28762306a36Sopenharmony_ci	u16 pump_min = 0, pump_max = 0xffff;
28862306a36Sopenharmony_ci	u16 tmp[4];
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	/* Try to fetch pumps min/max infos from eeprom */
29162306a36Sopenharmony_ci	if (mpu) {
29262306a36Sopenharmony_ci		memcpy(&tmp, mpu->processor_part_num, 8);
29362306a36Sopenharmony_ci		if (tmp[0] != 0xffff && tmp[1] != 0xffff) {
29462306a36Sopenharmony_ci			pump_min = max(pump_min, tmp[0]);
29562306a36Sopenharmony_ci			pump_max = min(pump_max, tmp[1]);
29662306a36Sopenharmony_ci		}
29762306a36Sopenharmony_ci		if (tmp[2] != 0xffff && tmp[3] != 0xffff) {
29862306a36Sopenharmony_ci			pump_min = max(pump_min, tmp[2]);
29962306a36Sopenharmony_ci			pump_max = min(pump_max, tmp[3]);
30062306a36Sopenharmony_ci		}
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	/* Double check the values, this _IS_ needed as the EEPROM on
30462306a36Sopenharmony_ci	 * some dual 2.5Ghz G5s seem, at least, to have both min & max
30562306a36Sopenharmony_ci	 * same to the same value ... (grrrr)
30662306a36Sopenharmony_ci	 */
30762306a36Sopenharmony_ci	if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) {
30862306a36Sopenharmony_ci		pump_min = CPU_PUMP_OUTPUT_MIN;
30962306a36Sopenharmony_ci		pump_max = CPU_PUMP_OUTPUT_MAX;
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	fan->min = pump_min;
31362306a36Sopenharmony_ci	fan->max = pump_max;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n",
31662306a36Sopenharmony_ci	    fan->ctrl.name, pump_min, pump_max);
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_cistatic void wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci	struct wf_fcu_priv *pv = fan->fcu_priv;
32262306a36Sopenharmony_ci	const struct mpu_data *mpu0 = wf_get_mpu(0);
32362306a36Sopenharmony_ci	const struct mpu_data *mpu1 = wf_get_mpu(1);
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	/* Default */
32662306a36Sopenharmony_ci	fan->min = 2400 >> pv->rpm_shift;
32762306a36Sopenharmony_ci	fan->max = 56000 >> pv->rpm_shift;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	/* CPU fans have min/max in MPU */
33062306a36Sopenharmony_ci	if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) {
33162306a36Sopenharmony_ci		fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan);
33262306a36Sopenharmony_ci		fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan);
33362306a36Sopenharmony_ci		goto bail;
33462306a36Sopenharmony_ci	}
33562306a36Sopenharmony_ci	if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) {
33662306a36Sopenharmony_ci		fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan);
33762306a36Sopenharmony_ci		fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan);
33862306a36Sopenharmony_ci		goto bail;
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci	if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) {
34162306a36Sopenharmony_ci		fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan);
34262306a36Sopenharmony_ci		fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan);
34362306a36Sopenharmony_ci		goto bail;
34462306a36Sopenharmony_ci	}
34562306a36Sopenharmony_ci	if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) {
34662306a36Sopenharmony_ci		fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan);
34762306a36Sopenharmony_ci		fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan);
34862306a36Sopenharmony_ci		goto bail;
34962306a36Sopenharmony_ci	}
35062306a36Sopenharmony_ci	/* Rackmac variants, we just use mpu0 intake */
35162306a36Sopenharmony_ci	if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) {
35262306a36Sopenharmony_ci		fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan);
35362306a36Sopenharmony_ci		fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan);
35462306a36Sopenharmony_ci		goto bail;
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci bail:
35762306a36Sopenharmony_ci	DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n",
35862306a36Sopenharmony_ci	    fan->ctrl.name, fan->min, fan->max);
35962306a36Sopenharmony_ci}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic void wf_fcu_add_fan(struct wf_fcu_priv *pv, const char *name,
36262306a36Sopenharmony_ci			   int type, int id)
36362306a36Sopenharmony_ci{
36462306a36Sopenharmony_ci	struct wf_fcu_fan *fan;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	fan = kzalloc(sizeof(*fan), GFP_KERNEL);
36762306a36Sopenharmony_ci	if (!fan)
36862306a36Sopenharmony_ci		return;
36962306a36Sopenharmony_ci	fan->fcu_priv = pv;
37062306a36Sopenharmony_ci	fan->id = id;
37162306a36Sopenharmony_ci	fan->ctrl.name = name;
37262306a36Sopenharmony_ci	fan->ctrl.priv = fan;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	/* min/max is oddball but the code comes from
37562306a36Sopenharmony_ci	 * therm_pm72 which seems to work so ...
37662306a36Sopenharmony_ci	 */
37762306a36Sopenharmony_ci	if (type == FCU_FAN_RPM) {
37862306a36Sopenharmony_ci		if (!strncmp(name, "cpu-pump", strlen("cpu-pump")))
37962306a36Sopenharmony_ci			wf_fcu_get_pump_minmax(fan);
38062306a36Sopenharmony_ci		else
38162306a36Sopenharmony_ci			wf_fcu_get_rpmfan_minmax(fan);
38262306a36Sopenharmony_ci		fan->ctrl.type = WF_CONTROL_RPM_FAN;
38362306a36Sopenharmony_ci		fan->ctrl.ops = &wf_fcu_fan_rpm_ops;
38462306a36Sopenharmony_ci	} else {
38562306a36Sopenharmony_ci		fan->min = 10;
38662306a36Sopenharmony_ci		fan->max = 100;
38762306a36Sopenharmony_ci		fan->ctrl.type = WF_CONTROL_PWM_FAN;
38862306a36Sopenharmony_ci		fan->ctrl.ops = &wf_fcu_fan_pwm_ops;
38962306a36Sopenharmony_ci	}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	if (wf_register_control(&fan->ctrl)) {
39262306a36Sopenharmony_ci		pr_err("wf_fcu: Failed to register fan %s\n", name);
39362306a36Sopenharmony_ci		kfree(fan);
39462306a36Sopenharmony_ci		return;
39562306a36Sopenharmony_ci	}
39662306a36Sopenharmony_ci	list_add(&fan->link, &pv->fan_list);
39762306a36Sopenharmony_ci	kref_get(&pv->ref);
39862306a36Sopenharmony_ci}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_cistatic void wf_fcu_lookup_fans(struct wf_fcu_priv *pv)
40162306a36Sopenharmony_ci{
40262306a36Sopenharmony_ci	/* Translation of device-tree location properties to
40362306a36Sopenharmony_ci	 * windfarm fan names
40462306a36Sopenharmony_ci	 */
40562306a36Sopenharmony_ci	static const struct {
40662306a36Sopenharmony_ci		const char *dt_name;	/* Device-tree name */
40762306a36Sopenharmony_ci		const char *ct_name;	/* Control name */
40862306a36Sopenharmony_ci	} loc_trans[] = {
40962306a36Sopenharmony_ci		{ "BACKSIDE",		"backside-fan",		},
41062306a36Sopenharmony_ci		{ "SYS CTRLR FAN",	"backside-fan",		},
41162306a36Sopenharmony_ci		{ "DRIVE BAY",		"drive-bay-fan",	},
41262306a36Sopenharmony_ci		{ "SLOT",		"slots-fan",		},
41362306a36Sopenharmony_ci		{ "PCI FAN",		"slots-fan",		},
41462306a36Sopenharmony_ci		{ "CPU A INTAKE",	"cpu-front-fan-0",	},
41562306a36Sopenharmony_ci		{ "CPU A EXHAUST",	"cpu-rear-fan-0",	},
41662306a36Sopenharmony_ci		{ "CPU B INTAKE",	"cpu-front-fan-1",	},
41762306a36Sopenharmony_ci		{ "CPU B EXHAUST",	"cpu-rear-fan-1",	},
41862306a36Sopenharmony_ci		{ "CPU A PUMP",		"cpu-pump-0",		},
41962306a36Sopenharmony_ci		{ "CPU B PUMP",		"cpu-pump-1",		},
42062306a36Sopenharmony_ci		{ "CPU A 1",		"cpu-fan-a-0",		},
42162306a36Sopenharmony_ci		{ "CPU A 2",		"cpu-fan-b-0",		},
42262306a36Sopenharmony_ci		{ "CPU A 3",		"cpu-fan-c-0",		},
42362306a36Sopenharmony_ci		{ "CPU B 1",		"cpu-fan-a-1",		},
42462306a36Sopenharmony_ci		{ "CPU B 2",		"cpu-fan-b-1",		},
42562306a36Sopenharmony_ci		{ "CPU B 3",		"cpu-fan-c-1",		},
42662306a36Sopenharmony_ci	};
42762306a36Sopenharmony_ci	struct device_node *np, *fcu = pv->i2c->dev.of_node;
42862306a36Sopenharmony_ci	int i;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	DBG("Looking up FCU controls in device-tree...\n");
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	for_each_child_of_node(fcu, np) {
43362306a36Sopenharmony_ci		int id, type = -1;
43462306a36Sopenharmony_ci		const char *loc;
43562306a36Sopenharmony_ci		const char *name;
43662306a36Sopenharmony_ci		const u32 *reg;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci		DBG(" control: %pOFn, type: %s\n", np, of_node_get_device_type(np));
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci		/* Detect control type */
44162306a36Sopenharmony_ci		if (of_node_is_type(np, "fan-rpm-control") ||
44262306a36Sopenharmony_ci		    of_node_is_type(np, "fan-rpm"))
44362306a36Sopenharmony_ci			type = FCU_FAN_RPM;
44462306a36Sopenharmony_ci		if (of_node_is_type(np, "fan-pwm-control") ||
44562306a36Sopenharmony_ci		    of_node_is_type(np, "fan-pwm"))
44662306a36Sopenharmony_ci			type = FCU_FAN_PWM;
44762306a36Sopenharmony_ci		/* Only care about fans for now */
44862306a36Sopenharmony_ci		if (type == -1)
44962306a36Sopenharmony_ci			continue;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci		/* Lookup for a matching location */
45262306a36Sopenharmony_ci		loc = of_get_property(np, "location", NULL);
45362306a36Sopenharmony_ci		reg = of_get_property(np, "reg", NULL);
45462306a36Sopenharmony_ci		if (loc == NULL || reg == NULL)
45562306a36Sopenharmony_ci			continue;
45662306a36Sopenharmony_ci		DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg);
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(loc_trans); i++) {
45962306a36Sopenharmony_ci			if (strncmp(loc, loc_trans[i].dt_name,
46062306a36Sopenharmony_ci				    strlen(loc_trans[i].dt_name)))
46162306a36Sopenharmony_ci				continue;
46262306a36Sopenharmony_ci			name = loc_trans[i].ct_name;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci			DBG(" location match, name: %s\n", name);
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci			if (type == FCU_FAN_RPM)
46762306a36Sopenharmony_ci				id = ((*reg) - 0x10) / 2;
46862306a36Sopenharmony_ci			else
46962306a36Sopenharmony_ci				id = ((*reg) - 0x30) / 2;
47062306a36Sopenharmony_ci			if (id > 7) {
47162306a36Sopenharmony_ci				pr_warn("wf_fcu: Can't parse fan ID in device-tree for %pOF\n", np);
47262306a36Sopenharmony_ci				break;
47362306a36Sopenharmony_ci			}
47462306a36Sopenharmony_ci			wf_fcu_add_fan(pv, name, type, id);
47562306a36Sopenharmony_ci			break;
47662306a36Sopenharmony_ci		}
47762306a36Sopenharmony_ci	}
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_cistatic void wf_fcu_default_fans(struct wf_fcu_priv *pv)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	/* We only support the default fans for PowerMac7,2 */
48362306a36Sopenharmony_ci	if (!of_machine_is_compatible("PowerMac7,2"))
48462306a36Sopenharmony_ci		return;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	wf_fcu_add_fan(pv, "backside-fan",	FCU_FAN_PWM, 1);
48762306a36Sopenharmony_ci	wf_fcu_add_fan(pv, "drive-bay-fan",	FCU_FAN_RPM, 2);
48862306a36Sopenharmony_ci	wf_fcu_add_fan(pv, "slots-fan",		FCU_FAN_PWM, 2);
48962306a36Sopenharmony_ci	wf_fcu_add_fan(pv, "cpu-front-fan-0",	FCU_FAN_RPM, 3);
49062306a36Sopenharmony_ci	wf_fcu_add_fan(pv, "cpu-rear-fan-0",	FCU_FAN_RPM, 4);
49162306a36Sopenharmony_ci	wf_fcu_add_fan(pv, "cpu-front-fan-1",	FCU_FAN_RPM, 5);
49262306a36Sopenharmony_ci	wf_fcu_add_fan(pv, "cpu-rear-fan-1",	FCU_FAN_RPM, 6);
49362306a36Sopenharmony_ci}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_cistatic int wf_fcu_init_chip(struct wf_fcu_priv *pv)
49662306a36Sopenharmony_ci{
49762306a36Sopenharmony_ci	unsigned char buf = 0xff;
49862306a36Sopenharmony_ci	int rc;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	rc = wf_fcu_write_reg(pv, 0xe, &buf, 1);
50162306a36Sopenharmony_ci	if (rc < 0)
50262306a36Sopenharmony_ci		return -EIO;
50362306a36Sopenharmony_ci	rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1);
50462306a36Sopenharmony_ci	if (rc < 0)
50562306a36Sopenharmony_ci		return -EIO;
50662306a36Sopenharmony_ci	rc = wf_fcu_read_reg(pv, 0, &buf, 1);
50762306a36Sopenharmony_ci	if (rc < 0)
50862306a36Sopenharmony_ci		return -EIO;
50962306a36Sopenharmony_ci	pv->rpm_shift = (buf == 1) ? 2 : 3;
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n",
51262306a36Sopenharmony_ci		 pv->rpm_shift);
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	return 0;
51562306a36Sopenharmony_ci}
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_cistatic int wf_fcu_probe(struct i2c_client *client)
51862306a36Sopenharmony_ci{
51962306a36Sopenharmony_ci	struct wf_fcu_priv *pv;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	pv = kzalloc(sizeof(*pv), GFP_KERNEL);
52262306a36Sopenharmony_ci	if (!pv)
52362306a36Sopenharmony_ci		return -ENOMEM;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	kref_init(&pv->ref);
52662306a36Sopenharmony_ci	mutex_init(&pv->lock);
52762306a36Sopenharmony_ci	INIT_LIST_HEAD(&pv->fan_list);
52862306a36Sopenharmony_ci	pv->i2c = client;
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	/*
53162306a36Sopenharmony_ci	 * First we must start the FCU which will query the
53262306a36Sopenharmony_ci	 * shift value to apply to RPMs
53362306a36Sopenharmony_ci	 */
53462306a36Sopenharmony_ci	if (wf_fcu_init_chip(pv)) {
53562306a36Sopenharmony_ci		pr_err("wf_fcu: Initialization failed !\n");
53662306a36Sopenharmony_ci		kfree(pv);
53762306a36Sopenharmony_ci		return -ENXIO;
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	/* First lookup fans in the device-tree */
54162306a36Sopenharmony_ci	wf_fcu_lookup_fans(pv);
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	/*
54462306a36Sopenharmony_ci	 * Older machines don't have the device-tree entries
54562306a36Sopenharmony_ci	 * we are looking for, just hard code the list
54662306a36Sopenharmony_ci	 */
54762306a36Sopenharmony_ci	if (list_empty(&pv->fan_list))
54862306a36Sopenharmony_ci		wf_fcu_default_fans(pv);
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	/* Still no fans ? FAIL */
55162306a36Sopenharmony_ci	if (list_empty(&pv->fan_list)) {
55262306a36Sopenharmony_ci		pr_err("wf_fcu: Failed to find fans for your machine\n");
55362306a36Sopenharmony_ci		kfree(pv);
55462306a36Sopenharmony_ci		return -ENODEV;
55562306a36Sopenharmony_ci	}
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	dev_set_drvdata(&client->dev, pv);
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	return 0;
56062306a36Sopenharmony_ci}
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_cistatic void wf_fcu_remove(struct i2c_client *client)
56362306a36Sopenharmony_ci{
56462306a36Sopenharmony_ci	struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev);
56562306a36Sopenharmony_ci	struct wf_fcu_fan *fan;
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci	while (!list_empty(&pv->fan_list)) {
56862306a36Sopenharmony_ci		fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link);
56962306a36Sopenharmony_ci		list_del(&fan->link);
57062306a36Sopenharmony_ci		wf_unregister_control(&fan->ctrl);
57162306a36Sopenharmony_ci	}
57262306a36Sopenharmony_ci	kref_put(&pv->ref, wf_fcu_release);
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_cistatic const struct i2c_device_id wf_fcu_id[] = {
57662306a36Sopenharmony_ci	{ "MAC,fcu", 0 },
57762306a36Sopenharmony_ci	{ }
57862306a36Sopenharmony_ci};
57962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, wf_fcu_id);
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_cistatic const struct of_device_id wf_fcu_of_id[] = {
58262306a36Sopenharmony_ci	{ .compatible = "fcu", },
58362306a36Sopenharmony_ci	{ }
58462306a36Sopenharmony_ci};
58562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, wf_fcu_of_id);
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_cistatic struct i2c_driver wf_fcu_driver = {
58862306a36Sopenharmony_ci	.driver = {
58962306a36Sopenharmony_ci		.name	= "wf_fcu",
59062306a36Sopenharmony_ci		.of_match_table = wf_fcu_of_id,
59162306a36Sopenharmony_ci	},
59262306a36Sopenharmony_ci	.probe		= wf_fcu_probe,
59362306a36Sopenharmony_ci	.remove		= wf_fcu_remove,
59462306a36Sopenharmony_ci	.id_table	= wf_fcu_id,
59562306a36Sopenharmony_ci};
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_cimodule_i2c_driver(wf_fcu_driver);
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
60062306a36Sopenharmony_ciMODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control");
60162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
60262306a36Sopenharmony_ci
603