162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Device driver for the i2c thermostat found on the iBook G4, Albook G4
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2003, 2004 Colin Leroy, Rasmus Rohde, Benjamin Herrenschmidt
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Documentation from 115254175ADT7467_pra.pdf and 3686221171167ADT7460_b.pdf
862306a36Sopenharmony_ci * https://www.onsemi.com/PowerSolutions/product.do?id=ADT7467
962306a36Sopenharmony_ci * https://www.onsemi.com/PowerSolutions/product.do?id=ADT7460
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/types.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/errno.h>
1662306a36Sopenharmony_ci#include <linux/kernel.h>
1762306a36Sopenharmony_ci#include <linux/delay.h>
1862306a36Sopenharmony_ci#include <linux/sched.h>
1962306a36Sopenharmony_ci#include <linux/i2c.h>
2062306a36Sopenharmony_ci#include <linux/slab.h>
2162306a36Sopenharmony_ci#include <linux/init.h>
2262306a36Sopenharmony_ci#include <linux/spinlock.h>
2362306a36Sopenharmony_ci#include <linux/wait.h>
2462306a36Sopenharmony_ci#include <linux/suspend.h>
2562306a36Sopenharmony_ci#include <linux/kthread.h>
2662306a36Sopenharmony_ci#include <linux/moduleparam.h>
2762306a36Sopenharmony_ci#include <linux/freezer.h>
2862306a36Sopenharmony_ci#include <linux/of.h>
2962306a36Sopenharmony_ci#include <linux/of_platform.h>
3062306a36Sopenharmony_ci#include <linux/platform_device.h>
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#include <asm/machdep.h>
3362306a36Sopenharmony_ci#include <asm/io.h>
3462306a36Sopenharmony_ci#include <asm/sections.h>
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#undef DEBUG
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define CONFIG_REG   0x40
3962306a36Sopenharmony_ci#define MANUAL_MASK  0xe0
4062306a36Sopenharmony_ci#define AUTO_MASK    0x20
4162306a36Sopenharmony_ci#define INVERT_MASK  0x10
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic u8 TEMP_REG[3]    = {0x26, 0x25, 0x27}; /* local, sensor1, sensor2 */
4462306a36Sopenharmony_cistatic u8 LIMIT_REG[3]   = {0x6b, 0x6a, 0x6c}; /* local, sensor1, sensor2 */
4562306a36Sopenharmony_cistatic u8 MANUAL_MODE[2] = {0x5c, 0x5d};
4662306a36Sopenharmony_cistatic u8 REM_CONTROL[2] = {0x00, 0x40};
4762306a36Sopenharmony_cistatic u8 FAN_SPEED[2]   = {0x28, 0x2a};
4862306a36Sopenharmony_cistatic u8 FAN_SPD_SET[2] = {0x30, 0x31};
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic u8 default_limits_local[3] = {70, 50, 70};    /* local, sensor1, sensor2 */
5162306a36Sopenharmony_cistatic u8 default_limits_chip[3] = {80, 65, 80};    /* local, sensor1, sensor2 */
5262306a36Sopenharmony_cistatic const char *sensor_location[3] = { "?", "?", "?" };
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic int limit_adjust;
5562306a36Sopenharmony_cistatic int fan_speed = -1;
5662306a36Sopenharmony_cistatic bool verbose;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ciMODULE_AUTHOR("Colin Leroy <colin@colino.net>");
5962306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for ADT746x thermostat in iBook G4 and "
6062306a36Sopenharmony_ci		   "Powerbook G4 Alu");
6162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cimodule_param(limit_adjust, int, 0644);
6462306a36Sopenharmony_ciMODULE_PARM_DESC(limit_adjust,"Adjust maximum temperatures (50 sensor1, 70 sensor2) "
6562306a36Sopenharmony_ci		 "by N degrees.");
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cimodule_param(fan_speed, int, 0644);
6862306a36Sopenharmony_ciMODULE_PARM_DESC(fan_speed,"Specify starting fan speed (0-255) "
6962306a36Sopenharmony_ci		 "(default 64)");
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cimodule_param(verbose, bool, 0);
7262306a36Sopenharmony_ciMODULE_PARM_DESC(verbose,"Verbose log operations "
7362306a36Sopenharmony_ci		 "(default 0)");
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistruct thermostat {
7662306a36Sopenharmony_ci	struct i2c_client	*clt;
7762306a36Sopenharmony_ci	u8			temps[3];
7862306a36Sopenharmony_ci	u8			cached_temp[3];
7962306a36Sopenharmony_ci	u8			initial_limits[3];
8062306a36Sopenharmony_ci	u8			limits[3];
8162306a36Sopenharmony_ci	int			last_speed[2];
8262306a36Sopenharmony_ci	int			last_var[2];
8362306a36Sopenharmony_ci	int			pwm_inv[2];
8462306a36Sopenharmony_ci	struct task_struct	*thread;
8562306a36Sopenharmony_ci	struct platform_device	*pdev;
8662306a36Sopenharmony_ci	enum {
8762306a36Sopenharmony_ci		ADT7460,
8862306a36Sopenharmony_ci		ADT7467
8962306a36Sopenharmony_ci	}			type;
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic void write_both_fan_speed(struct thermostat *th, int speed);
9362306a36Sopenharmony_cistatic void write_fan_speed(struct thermostat *th, int speed, int fan);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic int
9662306a36Sopenharmony_ciwrite_reg(struct thermostat* th, int reg, u8 data)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	u8 tmp[2];
9962306a36Sopenharmony_ci	int rc;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	tmp[0] = reg;
10262306a36Sopenharmony_ci	tmp[1] = data;
10362306a36Sopenharmony_ci	rc = i2c_master_send(th->clt, (const char *)tmp, 2);
10462306a36Sopenharmony_ci	if (rc < 0)
10562306a36Sopenharmony_ci		return rc;
10662306a36Sopenharmony_ci	if (rc != 2)
10762306a36Sopenharmony_ci		return -ENODEV;
10862306a36Sopenharmony_ci	return 0;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic int
11262306a36Sopenharmony_ciread_reg(struct thermostat* th, int reg)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	u8 reg_addr, data;
11562306a36Sopenharmony_ci	int rc;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	reg_addr = (u8)reg;
11862306a36Sopenharmony_ci	rc = i2c_master_send(th->clt, &reg_addr, 1);
11962306a36Sopenharmony_ci	if (rc < 0)
12062306a36Sopenharmony_ci		return rc;
12162306a36Sopenharmony_ci	if (rc != 1)
12262306a36Sopenharmony_ci		return -ENODEV;
12362306a36Sopenharmony_ci	rc = i2c_master_recv(th->clt, (char *)&data, 1);
12462306a36Sopenharmony_ci	if (rc < 0)
12562306a36Sopenharmony_ci		return rc;
12662306a36Sopenharmony_ci	return data;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int read_fan_speed(struct thermostat *th, u8 addr)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	u8 tmp[2];
13262306a36Sopenharmony_ci	u16 res;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/* should start with low byte */
13562306a36Sopenharmony_ci	tmp[1] = read_reg(th, addr);
13662306a36Sopenharmony_ci	tmp[0] = read_reg(th, addr + 1);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	res = tmp[1] + (tmp[0] << 8);
13962306a36Sopenharmony_ci	/* "a value of 0xffff means that the fan has stopped" */
14062306a36Sopenharmony_ci	return (res == 0xffff ? 0 : (90000*60)/res);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic void write_both_fan_speed(struct thermostat *th, int speed)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	write_fan_speed(th, speed, 0);
14662306a36Sopenharmony_ci	if (th->type == ADT7460)
14762306a36Sopenharmony_ci		write_fan_speed(th, speed, 1);
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic void write_fan_speed(struct thermostat *th, int speed, int fan)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	u8 manual;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (speed > 0xff)
15562306a36Sopenharmony_ci		speed = 0xff;
15662306a36Sopenharmony_ci	else if (speed < -1)
15762306a36Sopenharmony_ci		speed = 0;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (th->type == ADT7467 && fan == 1)
16062306a36Sopenharmony_ci		return;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if (th->last_speed[fan] != speed) {
16362306a36Sopenharmony_ci		if (verbose) {
16462306a36Sopenharmony_ci			if (speed == -1)
16562306a36Sopenharmony_ci				printk(KERN_DEBUG "adt746x: Setting speed to automatic "
16662306a36Sopenharmony_ci					"for %s fan.\n", sensor_location[fan+1]);
16762306a36Sopenharmony_ci			else
16862306a36Sopenharmony_ci				printk(KERN_DEBUG "adt746x: Setting speed to %d "
16962306a36Sopenharmony_ci					"for %s fan.\n", speed, sensor_location[fan+1]);
17062306a36Sopenharmony_ci		}
17162306a36Sopenharmony_ci	} else
17262306a36Sopenharmony_ci		return;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	if (speed >= 0) {
17562306a36Sopenharmony_ci		manual = read_reg(th, MANUAL_MODE[fan]);
17662306a36Sopenharmony_ci		manual &= ~INVERT_MASK;
17762306a36Sopenharmony_ci		write_reg(th, MANUAL_MODE[fan],
17862306a36Sopenharmony_ci			manual | MANUAL_MASK | th->pwm_inv[fan]);
17962306a36Sopenharmony_ci		write_reg(th, FAN_SPD_SET[fan], speed);
18062306a36Sopenharmony_ci	} else {
18162306a36Sopenharmony_ci		/* back to automatic */
18262306a36Sopenharmony_ci		if(th->type == ADT7460) {
18362306a36Sopenharmony_ci			manual = read_reg(th,
18462306a36Sopenharmony_ci				MANUAL_MODE[fan]) & (~MANUAL_MASK);
18562306a36Sopenharmony_ci			manual &= ~INVERT_MASK;
18662306a36Sopenharmony_ci			manual |= th->pwm_inv[fan];
18762306a36Sopenharmony_ci			write_reg(th,
18862306a36Sopenharmony_ci				MANUAL_MODE[fan], manual|REM_CONTROL[fan]);
18962306a36Sopenharmony_ci		} else {
19062306a36Sopenharmony_ci			manual = read_reg(th, MANUAL_MODE[fan]);
19162306a36Sopenharmony_ci			manual &= ~INVERT_MASK;
19262306a36Sopenharmony_ci			manual |= th->pwm_inv[fan];
19362306a36Sopenharmony_ci			write_reg(th, MANUAL_MODE[fan], manual&(~AUTO_MASK));
19462306a36Sopenharmony_ci		}
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	th->last_speed[fan] = speed;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic void read_sensors(struct thermostat *th)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	int i = 0;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	for (i = 0; i < 3; i++)
20562306a36Sopenharmony_ci		th->temps[i]  = read_reg(th, TEMP_REG[i]);
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci#ifdef DEBUG
20962306a36Sopenharmony_cistatic void display_stats(struct thermostat *th)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	if (th->temps[0] != th->cached_temp[0]
21262306a36Sopenharmony_ci	||  th->temps[1] != th->cached_temp[1]
21362306a36Sopenharmony_ci	||  th->temps[2] != th->cached_temp[2]) {
21462306a36Sopenharmony_ci		printk(KERN_INFO "adt746x: Temperature infos:"
21562306a36Sopenharmony_ci				 " thermostats: %d,%d,%d;"
21662306a36Sopenharmony_ci				 " limits: %d,%d,%d;"
21762306a36Sopenharmony_ci				 " fan speed: %d RPM\n",
21862306a36Sopenharmony_ci				 th->temps[0], th->temps[1], th->temps[2],
21962306a36Sopenharmony_ci				 th->limits[0],  th->limits[1],  th->limits[2],
22062306a36Sopenharmony_ci				 read_fan_speed(th, FAN_SPEED[0]));
22162306a36Sopenharmony_ci	}
22262306a36Sopenharmony_ci	th->cached_temp[0] = th->temps[0];
22362306a36Sopenharmony_ci	th->cached_temp[1] = th->temps[1];
22462306a36Sopenharmony_ci	th->cached_temp[2] = th->temps[2];
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci#endif
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic void update_fans_speed (struct thermostat *th)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	int lastvar = 0; /* last variation, for iBook */
23162306a36Sopenharmony_ci	int i = 0;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/* we don't care about local sensor, so we start at sensor 1 */
23462306a36Sopenharmony_ci	for (i = 1; i < 3; i++) {
23562306a36Sopenharmony_ci		bool started = false;
23662306a36Sopenharmony_ci		int fan_number = (th->type == ADT7460 && i == 2);
23762306a36Sopenharmony_ci		int var = th->temps[i] - th->limits[i];
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		if (var > -1) {
24062306a36Sopenharmony_ci			int step = (255 - fan_speed) / 7;
24162306a36Sopenharmony_ci			int new_speed = 0;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci			/* hysteresis : change fan speed only if variation is
24462306a36Sopenharmony_ci			 * more than two degrees */
24562306a36Sopenharmony_ci			if (abs(var - th->last_var[fan_number]) < 2)
24662306a36Sopenharmony_ci				continue;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci			started = true;
24962306a36Sopenharmony_ci			new_speed = fan_speed + ((var-1)*step);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci			if (new_speed < fan_speed)
25262306a36Sopenharmony_ci				new_speed = fan_speed;
25362306a36Sopenharmony_ci			if (new_speed > 255)
25462306a36Sopenharmony_ci				new_speed = 255;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci			if (verbose)
25762306a36Sopenharmony_ci				printk(KERN_DEBUG "adt746x: Setting fans speed to %d "
25862306a36Sopenharmony_ci						 "(limit exceeded by %d on %s)\n",
25962306a36Sopenharmony_ci						new_speed, var,
26062306a36Sopenharmony_ci						sensor_location[fan_number+1]);
26162306a36Sopenharmony_ci			write_both_fan_speed(th, new_speed);
26262306a36Sopenharmony_ci			th->last_var[fan_number] = var;
26362306a36Sopenharmony_ci		} else if (var < -2) {
26462306a36Sopenharmony_ci			/* don't stop fan if sensor2 is cold and sensor1 is not
26562306a36Sopenharmony_ci			 * so cold (lastvar >= -1) */
26662306a36Sopenharmony_ci			if (i == 2 && lastvar < -1) {
26762306a36Sopenharmony_ci				if (th->last_speed[fan_number] != 0)
26862306a36Sopenharmony_ci					if (verbose)
26962306a36Sopenharmony_ci						printk(KERN_DEBUG "adt746x: Stopping "
27062306a36Sopenharmony_ci							"fans.\n");
27162306a36Sopenharmony_ci				write_both_fan_speed(th, 0);
27262306a36Sopenharmony_ci			}
27362306a36Sopenharmony_ci		}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci		lastvar = var;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci		if (started)
27862306a36Sopenharmony_ci			return; /* we don't want to re-stop the fan
27962306a36Sopenharmony_ci				* if sensor1 is heating and sensor2 is not */
28062306a36Sopenharmony_ci	}
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic int monitor_task(void *arg)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	struct thermostat* th = arg;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	set_freezable();
28862306a36Sopenharmony_ci	while(!kthread_should_stop()) {
28962306a36Sopenharmony_ci		try_to_freeze();
29062306a36Sopenharmony_ci		msleep_interruptible(2000);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci#ifndef DEBUG
29362306a36Sopenharmony_ci		if (fan_speed != -1)
29462306a36Sopenharmony_ci			read_sensors(th);
29562306a36Sopenharmony_ci#else
29662306a36Sopenharmony_ci		read_sensors(th);
29762306a36Sopenharmony_ci#endif
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci		if (fan_speed != -1)
30062306a36Sopenharmony_ci			update_fans_speed(th);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci#ifdef DEBUG
30362306a36Sopenharmony_ci		display_stats(th);
30462306a36Sopenharmony_ci#endif
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	return 0;
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic void set_limit(struct thermostat *th, int i)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	/* Set sensor1 limit higher to avoid powerdowns */
31462306a36Sopenharmony_ci	th->limits[i] = default_limits_chip[i] + limit_adjust;
31562306a36Sopenharmony_ci	write_reg(th, LIMIT_REG[i], th->limits[i]);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	/* set our limits to normal */
31862306a36Sopenharmony_ci	th->limits[i] = default_limits_local[i] + limit_adjust;
31962306a36Sopenharmony_ci}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci#define BUILD_SHOW_FUNC_INT(name, data)				\
32262306a36Sopenharmony_cistatic ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)	\
32362306a36Sopenharmony_ci{								\
32462306a36Sopenharmony_ci	struct thermostat *th = dev_get_drvdata(dev);		\
32562306a36Sopenharmony_ci	return sprintf(buf, "%d\n", data);			\
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci#define BUILD_SHOW_FUNC_INT_LITE(name, data)				\
32962306a36Sopenharmony_cistatic ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)	\
33062306a36Sopenharmony_ci{								\
33162306a36Sopenharmony_ci	return sprintf(buf, "%d\n", data);			\
33262306a36Sopenharmony_ci}
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci#define BUILD_SHOW_FUNC_STR(name, data)				\
33562306a36Sopenharmony_cistatic ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)       \
33662306a36Sopenharmony_ci{								\
33762306a36Sopenharmony_ci	return sprintf(buf, "%s\n", data);			\
33862306a36Sopenharmony_ci}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci#define BUILD_SHOW_FUNC_FAN(name, data)				\
34162306a36Sopenharmony_cistatic ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)       \
34262306a36Sopenharmony_ci{								\
34362306a36Sopenharmony_ci	struct thermostat *th = dev_get_drvdata(dev);		\
34462306a36Sopenharmony_ci	return sprintf(buf, "%d (%d rpm)\n", 			\
34562306a36Sopenharmony_ci		th->last_speed[data],				\
34662306a36Sopenharmony_ci		read_fan_speed(th, FAN_SPEED[data])		\
34762306a36Sopenharmony_ci		);						\
34862306a36Sopenharmony_ci}
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci#define BUILD_STORE_FUNC_DEG(name, data)			\
35162306a36Sopenharmony_cistatic ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \
35262306a36Sopenharmony_ci{								\
35362306a36Sopenharmony_ci	struct thermostat *th = dev_get_drvdata(dev);		\
35462306a36Sopenharmony_ci	int val;						\
35562306a36Sopenharmony_ci	int i;							\
35662306a36Sopenharmony_ci	val = simple_strtol(buf, NULL, 10);			\
35762306a36Sopenharmony_ci	printk(KERN_INFO "Adjusting limits by %d degrees\n", val);	\
35862306a36Sopenharmony_ci	limit_adjust = val;					\
35962306a36Sopenharmony_ci	for (i=0; i < 3; i++)					\
36062306a36Sopenharmony_ci		set_limit(th, i);				\
36162306a36Sopenharmony_ci	return n;						\
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci#define BUILD_STORE_FUNC_INT(name, data)			\
36562306a36Sopenharmony_cistatic ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \
36662306a36Sopenharmony_ci{								\
36762306a36Sopenharmony_ci	int val;						\
36862306a36Sopenharmony_ci	val = simple_strtol(buf, NULL, 10);			\
36962306a36Sopenharmony_ci	if (val < 0 || val > 255)				\
37062306a36Sopenharmony_ci		return -EINVAL;					\
37162306a36Sopenharmony_ci	printk(KERN_INFO "Setting specified fan speed to %d\n", val);	\
37262306a36Sopenharmony_ci	data = val;						\
37362306a36Sopenharmony_ci	return n;						\
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ciBUILD_SHOW_FUNC_INT(sensor1_temperature,	 (read_reg(th, TEMP_REG[1])))
37762306a36Sopenharmony_ciBUILD_SHOW_FUNC_INT(sensor2_temperature,	 (read_reg(th, TEMP_REG[2])))
37862306a36Sopenharmony_ciBUILD_SHOW_FUNC_INT(sensor1_limit,		 th->limits[1])
37962306a36Sopenharmony_ciBUILD_SHOW_FUNC_INT(sensor2_limit,		 th->limits[2])
38062306a36Sopenharmony_ciBUILD_SHOW_FUNC_STR(sensor1_location,		 sensor_location[1])
38162306a36Sopenharmony_ciBUILD_SHOW_FUNC_STR(sensor2_location,		 sensor_location[2])
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ciBUILD_SHOW_FUNC_INT_LITE(specified_fan_speed, fan_speed)
38462306a36Sopenharmony_ciBUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed)
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ciBUILD_SHOW_FUNC_FAN(sensor1_fan_speed,	 0)
38762306a36Sopenharmony_ciBUILD_SHOW_FUNC_FAN(sensor2_fan_speed,	 1)
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ciBUILD_SHOW_FUNC_INT_LITE(limit_adjust,	 limit_adjust)
39062306a36Sopenharmony_ciBUILD_STORE_FUNC_DEG(limit_adjust,	 th)
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic DEVICE_ATTR(sensor1_temperature,	S_IRUGO,
39362306a36Sopenharmony_ci		   show_sensor1_temperature,NULL);
39462306a36Sopenharmony_cistatic DEVICE_ATTR(sensor2_temperature,	S_IRUGO,
39562306a36Sopenharmony_ci		   show_sensor2_temperature,NULL);
39662306a36Sopenharmony_cistatic DEVICE_ATTR(sensor1_limit, S_IRUGO,
39762306a36Sopenharmony_ci		   show_sensor1_limit,	NULL);
39862306a36Sopenharmony_cistatic DEVICE_ATTR(sensor2_limit, S_IRUGO,
39962306a36Sopenharmony_ci		   show_sensor2_limit,	NULL);
40062306a36Sopenharmony_cistatic DEVICE_ATTR(sensor1_location, S_IRUGO,
40162306a36Sopenharmony_ci		   show_sensor1_location, NULL);
40262306a36Sopenharmony_cistatic DEVICE_ATTR(sensor2_location, S_IRUGO,
40362306a36Sopenharmony_ci		   show_sensor2_location, NULL);
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_cistatic DEVICE_ATTR(specified_fan_speed,	S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
40662306a36Sopenharmony_ci		   show_specified_fan_speed,store_specified_fan_speed);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_cistatic DEVICE_ATTR(sensor1_fan_speed,	S_IRUGO,
40962306a36Sopenharmony_ci		   show_sensor1_fan_speed,	NULL);
41062306a36Sopenharmony_cistatic DEVICE_ATTR(sensor2_fan_speed,	S_IRUGO,
41162306a36Sopenharmony_ci		   show_sensor2_fan_speed,	NULL);
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_cistatic DEVICE_ATTR(limit_adjust,	S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
41462306a36Sopenharmony_ci		   show_limit_adjust,	store_limit_adjust);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic void thermostat_create_files(struct thermostat *th)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	struct device_node *np = th->clt->dev.of_node;
41962306a36Sopenharmony_ci	struct device *dev;
42062306a36Sopenharmony_ci	int err;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	/* To maintain ABI compatibility with userspace, create
42362306a36Sopenharmony_ci	 * the old style platform driver and attach the attributes
42462306a36Sopenharmony_ci	 * to it here
42562306a36Sopenharmony_ci	 */
42662306a36Sopenharmony_ci	th->pdev = of_platform_device_create(np, "temperatures", NULL);
42762306a36Sopenharmony_ci	if (!th->pdev)
42862306a36Sopenharmony_ci		return;
42962306a36Sopenharmony_ci	dev = &th->pdev->dev;
43062306a36Sopenharmony_ci	dev_set_drvdata(dev, th);
43162306a36Sopenharmony_ci	err = device_create_file(dev, &dev_attr_sensor1_temperature);
43262306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_sensor2_temperature);
43362306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_sensor1_limit);
43462306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_sensor2_limit);
43562306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_sensor1_location);
43662306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_sensor2_location);
43762306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_limit_adjust);
43862306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_specified_fan_speed);
43962306a36Sopenharmony_ci	err |= device_create_file(dev, &dev_attr_sensor1_fan_speed);
44062306a36Sopenharmony_ci	if(th->type == ADT7460)
44162306a36Sopenharmony_ci		err |= device_create_file(dev, &dev_attr_sensor2_fan_speed);
44262306a36Sopenharmony_ci	if (err)
44362306a36Sopenharmony_ci		printk(KERN_WARNING
44462306a36Sopenharmony_ci			"Failed to create temperature attribute file(s).\n");
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_cistatic void thermostat_remove_files(struct thermostat *th)
44862306a36Sopenharmony_ci{
44962306a36Sopenharmony_ci	struct device *dev;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	if (!th->pdev)
45262306a36Sopenharmony_ci		return;
45362306a36Sopenharmony_ci	dev = &th->pdev->dev;
45462306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_sensor1_temperature);
45562306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_sensor2_temperature);
45662306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_sensor1_limit);
45762306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_sensor2_limit);
45862306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_sensor1_location);
45962306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_sensor2_location);
46062306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_limit_adjust);
46162306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_specified_fan_speed);
46262306a36Sopenharmony_ci	device_remove_file(dev, &dev_attr_sensor1_fan_speed);
46362306a36Sopenharmony_ci	if (th->type == ADT7460)
46462306a36Sopenharmony_ci		device_remove_file(dev, &dev_attr_sensor2_fan_speed);
46562306a36Sopenharmony_ci	of_device_unregister(th->pdev);
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci}
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_cistatic int probe_thermostat(struct i2c_client *client)
47062306a36Sopenharmony_ci{
47162306a36Sopenharmony_ci	const struct i2c_device_id *id = i2c_client_get_device_id(client);
47262306a36Sopenharmony_ci	struct device_node *np = client->dev.of_node;
47362306a36Sopenharmony_ci	struct thermostat* th;
47462306a36Sopenharmony_ci	const __be32 *prop;
47562306a36Sopenharmony_ci	int i, rc, vers, offset = 0;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	if (!np)
47862306a36Sopenharmony_ci		return -ENXIO;
47962306a36Sopenharmony_ci	prop = of_get_property(np, "hwsensor-params-version", NULL);
48062306a36Sopenharmony_ci	if (!prop)
48162306a36Sopenharmony_ci		return -ENXIO;
48262306a36Sopenharmony_ci	vers = be32_to_cpup(prop);
48362306a36Sopenharmony_ci	printk(KERN_INFO "adt746x: version %d (%ssupported)\n",
48462306a36Sopenharmony_ci	       vers, vers == 1 ? "" : "un");
48562306a36Sopenharmony_ci	if (vers != 1)
48662306a36Sopenharmony_ci		return -ENXIO;
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	if (of_property_present(np, "hwsensor-location")) {
48962306a36Sopenharmony_ci		for (i = 0; i < 3; i++) {
49062306a36Sopenharmony_ci			sensor_location[i] = of_get_property(np,
49162306a36Sopenharmony_ci					"hwsensor-location", NULL) + offset;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci			if (sensor_location[i] == NULL)
49462306a36Sopenharmony_ci				sensor_location[i] = "";
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci			printk(KERN_INFO "sensor %d: %s\n", i, sensor_location[i]);
49762306a36Sopenharmony_ci			offset += strlen(sensor_location[i]) + 1;
49862306a36Sopenharmony_ci		}
49962306a36Sopenharmony_ci	}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	th = kzalloc(sizeof(struct thermostat), GFP_KERNEL);
50262306a36Sopenharmony_ci	if (!th)
50362306a36Sopenharmony_ci		return -ENOMEM;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	i2c_set_clientdata(client, th);
50662306a36Sopenharmony_ci	th->clt = client;
50762306a36Sopenharmony_ci	th->type = id->driver_data;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	rc = read_reg(th, CONFIG_REG);
51062306a36Sopenharmony_ci	if (rc < 0) {
51162306a36Sopenharmony_ci		dev_err(&client->dev, "Thermostat failed to read config!\n");
51262306a36Sopenharmony_ci		kfree(th);
51362306a36Sopenharmony_ci		return -ENODEV;
51462306a36Sopenharmony_ci	}
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	/* force manual control to start the fan quieter */
51762306a36Sopenharmony_ci	if (fan_speed == -1)
51862306a36Sopenharmony_ci		fan_speed = 64;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	if (th->type == ADT7460) {
52162306a36Sopenharmony_ci		printk(KERN_INFO "adt746x: ADT7460 initializing\n");
52262306a36Sopenharmony_ci		/* The 7460 needs to be started explicitly */
52362306a36Sopenharmony_ci		write_reg(th, CONFIG_REG, 1);
52462306a36Sopenharmony_ci	} else
52562306a36Sopenharmony_ci		printk(KERN_INFO "adt746x: ADT7467 initializing\n");
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	for (i = 0; i < 3; i++) {
52862306a36Sopenharmony_ci		th->initial_limits[i] = read_reg(th, LIMIT_REG[i]);
52962306a36Sopenharmony_ci		set_limit(th, i);
53062306a36Sopenharmony_ci	}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d"
53362306a36Sopenharmony_ci			 " to %d, %d, %d\n",
53462306a36Sopenharmony_ci			 th->initial_limits[0], th->initial_limits[1],
53562306a36Sopenharmony_ci			 th->initial_limits[2], th->limits[0], th->limits[1],
53662306a36Sopenharmony_ci			 th->limits[2]);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	/* record invert bit status because fw can corrupt it after suspend */
53962306a36Sopenharmony_ci	th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK;
54062306a36Sopenharmony_ci	th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK;
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	/* be sure to really write fan speed the first time */
54362306a36Sopenharmony_ci	th->last_speed[0] = -2;
54462306a36Sopenharmony_ci	th->last_speed[1] = -2;
54562306a36Sopenharmony_ci	th->last_var[0] = -80;
54662306a36Sopenharmony_ci	th->last_var[1] = -80;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	if (fan_speed != -1) {
54962306a36Sopenharmony_ci		/* manual mode, stop fans */
55062306a36Sopenharmony_ci		write_both_fan_speed(th, 0);
55162306a36Sopenharmony_ci	} else {
55262306a36Sopenharmony_ci		/* automatic mode */
55362306a36Sopenharmony_ci		write_both_fan_speed(th, -1);
55462306a36Sopenharmony_ci	}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	th->thread = kthread_run(monitor_task, th, "kfand");
55762306a36Sopenharmony_ci	if (th->thread == ERR_PTR(-ENOMEM)) {
55862306a36Sopenharmony_ci		printk(KERN_INFO "adt746x: Kthread creation failed\n");
55962306a36Sopenharmony_ci		th->thread = NULL;
56062306a36Sopenharmony_ci		return -ENOMEM;
56162306a36Sopenharmony_ci	}
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	thermostat_create_files(th);
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ci	return 0;
56662306a36Sopenharmony_ci}
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_cistatic void remove_thermostat(struct i2c_client *client)
56962306a36Sopenharmony_ci{
57062306a36Sopenharmony_ci	struct thermostat *th = i2c_get_clientdata(client);
57162306a36Sopenharmony_ci	int i;
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	thermostat_remove_files(th);
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	if (th->thread != NULL)
57662306a36Sopenharmony_ci		kthread_stop(th->thread);
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci	printk(KERN_INFO "adt746x: Putting max temperatures back from "
57962306a36Sopenharmony_ci			 "%d, %d, %d to %d, %d, %d\n",
58062306a36Sopenharmony_ci		th->limits[0], th->limits[1], th->limits[2],
58162306a36Sopenharmony_ci		th->initial_limits[0], th->initial_limits[1],
58262306a36Sopenharmony_ci		th->initial_limits[2]);
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	for (i = 0; i < 3; i++)
58562306a36Sopenharmony_ci		write_reg(th, LIMIT_REG[i], th->initial_limits[i]);
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	write_both_fan_speed(th, -1);
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	kfree(th);
59062306a36Sopenharmony_ci}
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_cistatic const struct i2c_device_id therm_adt746x_id[] = {
59362306a36Sopenharmony_ci	{ "MAC,adt7460", ADT7460 },
59462306a36Sopenharmony_ci	{ "MAC,adt7467", ADT7467 },
59562306a36Sopenharmony_ci	{ }
59662306a36Sopenharmony_ci};
59762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, therm_adt746x_id);
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_cistatic struct i2c_driver thermostat_driver = {
60062306a36Sopenharmony_ci	.driver = {
60162306a36Sopenharmony_ci		.name	= "therm_adt746x",
60262306a36Sopenharmony_ci	},
60362306a36Sopenharmony_ci	.probe = probe_thermostat,
60462306a36Sopenharmony_ci	.remove = remove_thermostat,
60562306a36Sopenharmony_ci	.id_table = therm_adt746x_id,
60662306a36Sopenharmony_ci};
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_cistatic int __init thermostat_init(void)
60962306a36Sopenharmony_ci{
61062306a36Sopenharmony_ci#ifndef CONFIG_I2C_POWERMAC
61162306a36Sopenharmony_ci	request_module("i2c-powermac");
61262306a36Sopenharmony_ci#endif
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	return i2c_add_driver(&thermostat_driver);
61562306a36Sopenharmony_ci}
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_cistatic void __exit thermostat_exit(void)
61862306a36Sopenharmony_ci{
61962306a36Sopenharmony_ci	i2c_del_driver(&thermostat_driver);
62062306a36Sopenharmony_ci}
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_cimodule_init(thermostat_init);
62362306a36Sopenharmony_cimodule_exit(thermostat_exit);
624