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, ®_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