162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Windfarm PowerMac thermal control. SMU based controls
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
662306a36Sopenharmony_ci *                    <benh@kernel.crashing.org>
762306a36Sopenharmony_ci */
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/completion.h>
1762306a36Sopenharmony_ci#include <linux/of.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <asm/machdep.h>
2062306a36Sopenharmony_ci#include <asm/io.h>
2162306a36Sopenharmony_ci#include <asm/sections.h>
2262306a36Sopenharmony_ci#include <asm/smu.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include "windfarm.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define VERSION "0.4"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#undef DEBUG
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#ifdef DEBUG
3162306a36Sopenharmony_ci#define DBG(args...)	printk(args)
3262306a36Sopenharmony_ci#else
3362306a36Sopenharmony_ci#define DBG(args...)	do { } while(0)
3462306a36Sopenharmony_ci#endif
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int smu_supports_new_fans_ops = 1;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/*
3962306a36Sopenharmony_ci * SMU fans control object
4062306a36Sopenharmony_ci */
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic LIST_HEAD(smu_fans);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistruct smu_fan_control {
4562306a36Sopenharmony_ci	struct list_head	link;
4662306a36Sopenharmony_ci	int    			fan_type;	/* 0 = rpm, 1 = pwm */
4762306a36Sopenharmony_ci	u32			reg;		/* index in SMU */
4862306a36Sopenharmony_ci	s32			value;		/* current value */
4962306a36Sopenharmony_ci	s32			min, max;	/* min/max values */
5062306a36Sopenharmony_ci	struct wf_control	ctrl;
5162306a36Sopenharmony_ci};
5262306a36Sopenharmony_ci#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic int smu_set_fan(int pwm, u8 id, u16 value)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct smu_cmd cmd;
5762306a36Sopenharmony_ci	u8 buffer[16];
5862306a36Sopenharmony_ci	DECLARE_COMPLETION_ONSTACK(comp);
5962306a36Sopenharmony_ci	int rc;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/* Fill SMU command structure */
6262306a36Sopenharmony_ci	cmd.cmd = SMU_CMD_FAN_COMMAND;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/* The SMU has an "old" and a "new" way of setting the fan speed
6562306a36Sopenharmony_ci	 * Unfortunately, I found no reliable way to know which one works
6662306a36Sopenharmony_ci	 * on a given machine model. After some investigations it appears
6762306a36Sopenharmony_ci	 * that MacOS X just tries the new one, and if it fails fallbacks
6862306a36Sopenharmony_ci	 * to the old ones ... Ugh.
6962306a36Sopenharmony_ci	 */
7062306a36Sopenharmony_ci retry:
7162306a36Sopenharmony_ci	if (smu_supports_new_fans_ops) {
7262306a36Sopenharmony_ci		buffer[0] = 0x30;
7362306a36Sopenharmony_ci		buffer[1] = id;
7462306a36Sopenharmony_ci		*((u16 *)(&buffer[2])) = value;
7562306a36Sopenharmony_ci		cmd.data_len = 4;
7662306a36Sopenharmony_ci	} else {
7762306a36Sopenharmony_ci		if (id > 7)
7862306a36Sopenharmony_ci			return -EINVAL;
7962306a36Sopenharmony_ci		/* Fill argument buffer */
8062306a36Sopenharmony_ci		memset(buffer, 0, 16);
8162306a36Sopenharmony_ci		buffer[0] = pwm ? 0x10 : 0x00;
8262306a36Sopenharmony_ci		buffer[1] = 0x01 << id;
8362306a36Sopenharmony_ci		*((u16 *)&buffer[2 + id * 2]) = value;
8462306a36Sopenharmony_ci		cmd.data_len = 14;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	cmd.reply_len = 16;
8862306a36Sopenharmony_ci	cmd.data_buf = cmd.reply_buf = buffer;
8962306a36Sopenharmony_ci	cmd.status = 0;
9062306a36Sopenharmony_ci	cmd.done = smu_done_complete;
9162306a36Sopenharmony_ci	cmd.misc = &comp;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	rc = smu_queue_cmd(&cmd);
9462306a36Sopenharmony_ci	if (rc)
9562306a36Sopenharmony_ci		return rc;
9662306a36Sopenharmony_ci	wait_for_completion(&comp);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/* Handle fallback (see comment above) */
9962306a36Sopenharmony_ci	if (cmd.status != 0 && smu_supports_new_fans_ops) {
10062306a36Sopenharmony_ci		printk(KERN_WARNING "windfarm: SMU failed new fan command "
10162306a36Sopenharmony_ci		       "falling back to old method\n");
10262306a36Sopenharmony_ci		smu_supports_new_fans_ops = 0;
10362306a36Sopenharmony_ci		goto retry;
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	return cmd.status;
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void smu_fan_release(struct wf_control *ct)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	kfree(fct);
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic int smu_fan_set(struct wf_control *ct, s32 value)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	if (value < fct->min)
12162306a36Sopenharmony_ci		value = fct->min;
12262306a36Sopenharmony_ci	if (value > fct->max)
12362306a36Sopenharmony_ci		value = fct->max;
12462306a36Sopenharmony_ci	fct->value = value;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return smu_set_fan(fct->fan_type, fct->reg, value);
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int smu_fan_get(struct wf_control *ct, s32 *value)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
13262306a36Sopenharmony_ci	*value = fct->value; /* todo: read from SMU */
13362306a36Sopenharmony_ci	return 0;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic s32 smu_fan_min(struct wf_control *ct)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
13962306a36Sopenharmony_ci	return fct->min;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic s32 smu_fan_max(struct wf_control *ct)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
14562306a36Sopenharmony_ci	return fct->max;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic const struct wf_control_ops smu_fan_ops = {
14962306a36Sopenharmony_ci	.set_value	= smu_fan_set,
15062306a36Sopenharmony_ci	.get_value	= smu_fan_get,
15162306a36Sopenharmony_ci	.get_min	= smu_fan_min,
15262306a36Sopenharmony_ci	.get_max	= smu_fan_max,
15362306a36Sopenharmony_ci	.release	= smu_fan_release,
15462306a36Sopenharmony_ci	.owner		= THIS_MODULE,
15562306a36Sopenharmony_ci};
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic struct smu_fan_control *smu_fan_create(struct device_node *node,
15862306a36Sopenharmony_ci					      int pwm_fan)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct smu_fan_control *fct;
16162306a36Sopenharmony_ci	const s32 *v;
16262306a36Sopenharmony_ci	const u32 *reg;
16362306a36Sopenharmony_ci	const char *l;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL);
16662306a36Sopenharmony_ci	if (fct == NULL)
16762306a36Sopenharmony_ci		return NULL;
16862306a36Sopenharmony_ci	fct->ctrl.ops = &smu_fan_ops;
16962306a36Sopenharmony_ci	l = of_get_property(node, "location", NULL);
17062306a36Sopenharmony_ci	if (l == NULL)
17162306a36Sopenharmony_ci		goto fail;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	fct->fan_type = pwm_fan;
17462306a36Sopenharmony_ci	fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	/* We use the name & location here the same way we do for SMU sensors,
17762306a36Sopenharmony_ci	 * see the comment in windfarm_smu_sensors.c. The locations are a bit
17862306a36Sopenharmony_ci	 * less consistent here between the iMac and the desktop models, but
17962306a36Sopenharmony_ci	 * that is good enough for our needs for now at least.
18062306a36Sopenharmony_ci	 *
18162306a36Sopenharmony_ci	 * One problem though is that Apple seem to be inconsistent with case
18262306a36Sopenharmony_ci	 * and the kernel doesn't have strcasecmp =P
18362306a36Sopenharmony_ci	 */
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	fct->ctrl.name = NULL;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* Names used on desktop models */
18862306a36Sopenharmony_ci	if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") ||
18962306a36Sopenharmony_ci	    !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan") ||
19062306a36Sopenharmony_ci	    !strcmp(l, "CPU A EXHAUST"))
19162306a36Sopenharmony_ci		fct->ctrl.name = "cpu-rear-fan-0";
19262306a36Sopenharmony_ci	else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1") ||
19362306a36Sopenharmony_ci		 !strcmp(l, "CPU B EXHAUST"))
19462306a36Sopenharmony_ci		fct->ctrl.name = "cpu-rear-fan-1";
19562306a36Sopenharmony_ci	else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") ||
19662306a36Sopenharmony_ci		 !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan") ||
19762306a36Sopenharmony_ci		 !strcmp(l, "CPU A INTAKE"))
19862306a36Sopenharmony_ci		fct->ctrl.name = "cpu-front-fan-0";
19962306a36Sopenharmony_ci	else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1") ||
20062306a36Sopenharmony_ci		 !strcmp(l, "CPU B INTAKE"))
20162306a36Sopenharmony_ci		fct->ctrl.name = "cpu-front-fan-1";
20262306a36Sopenharmony_ci	else if (!strcmp(l, "CPU A PUMP"))
20362306a36Sopenharmony_ci		fct->ctrl.name = "cpu-pump-0";
20462306a36Sopenharmony_ci	else if (!strcmp(l, "CPU B PUMP"))
20562306a36Sopenharmony_ci		fct->ctrl.name = "cpu-pump-1";
20662306a36Sopenharmony_ci	else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan") ||
20762306a36Sopenharmony_ci		 !strcmp(l, "EXPANSION SLOTS INTAKE"))
20862306a36Sopenharmony_ci		fct->ctrl.name = "slots-fan";
20962306a36Sopenharmony_ci	else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay") ||
21062306a36Sopenharmony_ci		 !strcmp(l, "DRIVE BAY A INTAKE"))
21162306a36Sopenharmony_ci		fct->ctrl.name = "drive-bay-fan";
21262306a36Sopenharmony_ci	else if (!strcmp(l, "BACKSIDE"))
21362306a36Sopenharmony_ci		fct->ctrl.name = "backside-fan";
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	/* Names used on iMac models */
21662306a36Sopenharmony_ci	if (!strcmp(l, "System Fan") || !strcmp(l, "System fan"))
21762306a36Sopenharmony_ci		fct->ctrl.name = "system-fan";
21862306a36Sopenharmony_ci	else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan"))
21962306a36Sopenharmony_ci		fct->ctrl.name = "cpu-fan";
22062306a36Sopenharmony_ci	else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive"))
22162306a36Sopenharmony_ci		fct->ctrl.name = "drive-bay-fan";
22262306a36Sopenharmony_ci	else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */
22362306a36Sopenharmony_ci		fct->ctrl.name = "hard-drive-fan";
22462306a36Sopenharmony_ci	else if (!strcmp(l, "ODD Fan")) /* same */
22562306a36Sopenharmony_ci		fct->ctrl.name = "optical-drive-fan";
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	/* Unrecognized fan, bail out */
22862306a36Sopenharmony_ci	if (fct->ctrl.name == NULL)
22962306a36Sopenharmony_ci		goto fail;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* Get min & max values*/
23262306a36Sopenharmony_ci	v = of_get_property(node, "min-value", NULL);
23362306a36Sopenharmony_ci	if (v == NULL)
23462306a36Sopenharmony_ci		goto fail;
23562306a36Sopenharmony_ci	fct->min = *v;
23662306a36Sopenharmony_ci	v = of_get_property(node, "max-value", NULL);
23762306a36Sopenharmony_ci	if (v == NULL)
23862306a36Sopenharmony_ci		goto fail;
23962306a36Sopenharmony_ci	fct->max = *v;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	/* Get "reg" value */
24262306a36Sopenharmony_ci	reg = of_get_property(node, "reg", NULL);
24362306a36Sopenharmony_ci	if (reg == NULL)
24462306a36Sopenharmony_ci		goto fail;
24562306a36Sopenharmony_ci	fct->reg = *reg;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	if (wf_register_control(&fct->ctrl))
24862306a36Sopenharmony_ci		goto fail;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	return fct;
25162306a36Sopenharmony_ci fail:
25262306a36Sopenharmony_ci	kfree(fct);
25362306a36Sopenharmony_ci	return NULL;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic int __init smu_controls_init(void)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	struct device_node *smu, *fans, *fan;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	if (!smu_present())
26262306a36Sopenharmony_ci		return -ENODEV;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	smu = of_find_node_by_type(NULL, "smu");
26562306a36Sopenharmony_ci	if (smu == NULL)
26662306a36Sopenharmony_ci		return -ENODEV;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	/* Look for RPM fans */
26962306a36Sopenharmony_ci	for_each_child_of_node(smu, fans)
27062306a36Sopenharmony_ci		if (of_node_name_eq(fans, "rpm-fans") ||
27162306a36Sopenharmony_ci		    of_device_is_compatible(fans, "smu-rpm-fans"))
27262306a36Sopenharmony_ci			break;
27362306a36Sopenharmony_ci	for_each_child_of_node(fans, fan) {
27462306a36Sopenharmony_ci		struct smu_fan_control *fct;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci		fct = smu_fan_create(fan, 0);
27762306a36Sopenharmony_ci		if (fct == NULL) {
27862306a36Sopenharmony_ci			printk(KERN_WARNING "windfarm: Failed to create SMU "
27962306a36Sopenharmony_ci			       "RPM fan %pOFn\n", fan);
28062306a36Sopenharmony_ci			continue;
28162306a36Sopenharmony_ci		}
28262306a36Sopenharmony_ci		list_add(&fct->link, &smu_fans);
28362306a36Sopenharmony_ci	}
28462306a36Sopenharmony_ci	of_node_put(fans);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	/* Look for PWM fans */
28862306a36Sopenharmony_ci	for_each_child_of_node(smu, fans)
28962306a36Sopenharmony_ci		if (of_node_name_eq(fans, "pwm-fans"))
29062306a36Sopenharmony_ci			break;
29162306a36Sopenharmony_ci	for_each_child_of_node(fans, fan) {
29262306a36Sopenharmony_ci		struct smu_fan_control *fct;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci		fct = smu_fan_create(fan, 1);
29562306a36Sopenharmony_ci		if (fct == NULL) {
29662306a36Sopenharmony_ci			printk(KERN_WARNING "windfarm: Failed to create SMU "
29762306a36Sopenharmony_ci			       "PWM fan %pOFn\n", fan);
29862306a36Sopenharmony_ci			continue;
29962306a36Sopenharmony_ci		}
30062306a36Sopenharmony_ci		list_add(&fct->link, &smu_fans);
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci	of_node_put(fans);
30362306a36Sopenharmony_ci	of_node_put(smu);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	return 0;
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic void __exit smu_controls_exit(void)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	struct smu_fan_control *fct;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	while (!list_empty(&smu_fans)) {
31362306a36Sopenharmony_ci		fct = list_entry(smu_fans.next, struct smu_fan_control, link);
31462306a36Sopenharmony_ci		list_del(&fct->link);
31562306a36Sopenharmony_ci		wf_unregister_control(&fct->ctrl);
31662306a36Sopenharmony_ci	}
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_cimodule_init(smu_controls_init);
32162306a36Sopenharmony_cimodule_exit(smu_controls_exit);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
32462306a36Sopenharmony_ciMODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control");
32562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
32662306a36Sopenharmony_ci
327