/*
* adt7475 - Thermal sensor driver for the ADT7475 chip and derivatives
* Copyright (C) 2007-2008, Advanced Micro Devices, Inc.
* Copyright (C) 2008 Jordan Crouse <jordan@cosmicpenguin.net>
* Copyright (C) 2008 Hans de Goede <hdegoede@redhat.com>
* Derived from the lm83 driver by Jean Delvare
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
/* Indexes for the sysfs hooks */
#define INPUT 0
#define MIN 1
#define MAX 2
#define CONTROL 3
#define OFFSET 3
#define AUTOMIN 4
#define THERM 5
#define HYSTERSIS 6
/* These are unique identifiers for the sysfs functions - unlike the
numbers above, these are not also indexes into an array
*/
#define ALARM 9
#define FAULT 10
/* 7475 Common Registers */
#define REG_VOLTAGE_BASE 0x21
#define REG_TEMP_BASE 0x25
#define REG_TACH_BASE 0x28
#define REG_PWM_BASE 0x30
#define REG_PWM_MAX_BASE 0x38
#define REG_DEVID 0x3D
#define REG_VENDID 0x3E
#define REG_STATUS1 0x41
#define REG_STATUS2 0x42
#define REG_VOLTAGE_MIN_BASE 0x46
#define REG_VOLTAGE_MAX_BASE 0x47
#define REG_TEMP_MIN_BASE 0x4E
#define REG_TEMP_MAX_BASE 0x4F
#define REG_TACH_MIN_BASE 0x54
#define REG_PWM_CONFIG_BASE 0x5C
#define REG_TEMP_TRANGE_BASE 0x5F
#define REG_PWM_MIN_BASE 0x64
#define REG_TEMP_TMIN_BASE 0x67
#define REG_TEMP_THERM_BASE 0x6A
#define REG_REMOTE1_HYSTERSIS 0x6D
#define REG_REMOTE2_HYSTERSIS 0x6E
#define REG_TEMP_OFFSET_BASE 0x70
#define REG_EXTEND1 0x76
#define REG_EXTEND2 0x77
#define REG_CONFIG5 0x7C
#define CONFIG5_TWOSCOMP 0x01
#define CONFIG5_TEMPOFFSET 0x02
/* ADT7475 Settings */
#define ADT7475_VOLTAGE_COUNT 2
#define ADT7475_TEMP_COUNT 3
#define ADT7475_TACH_COUNT 4
#define ADT7475_PWM_COUNT 3
/* Macro to read the registers */
#define adt7475_read(reg) i2c_smbus_read_byte_data(client, (reg))
/* Macros to easily index the registers */
#define TACH_REG(idx) (REG_TACH_BASE + ((idx) * 2))
#define TACH_MIN_REG(idx) (REG_TACH_MIN_BASE + ((idx) * 2))
#define PWM_REG(idx) (REG_PWM_BASE + (idx))
#define PWM_MAX_REG(idx) (REG_PWM_MAX_BASE + (idx))
#define PWM_MIN_REG(idx) (REG_PWM_MIN_BASE + (idx))
#define PWM_CONFIG_REG(idx) (REG_PWM_CONFIG_BASE + (idx))
#define VOLTAGE_REG(idx) (REG_VOLTAGE_BASE + (idx))
#define VOLTAGE_MIN_REG(idx) (REG_VOLTAGE_MIN_BASE + ((idx) * 2))
#define VOLTAGE_MAX_REG(idx) (REG_VOLTAGE_MAX_BASE + ((idx) * 2))
#define TEMP_REG(idx) (REG_TEMP_BASE + (idx))
#define TEMP_MIN_REG(idx) (REG_TEMP_MIN_BASE + ((idx) * 2))
#define TEMP_MAX_REG(idx) (REG_TEMP_MAX_BASE + ((idx) * 2))
#define TEMP_TMIN_REG(idx) (REG_TEMP_TMIN_BASE + (idx))
#define TEMP_THERM_REG(idx) (REG_TEMP_THERM_BASE + (idx))
#define TEMP_OFFSET_REG(idx) (REG_TEMP_OFFSET_BASE + (idx))
#define TEMP_TRANGE_REG(idx) (REG_TEMP_TRANGE_BASE + (idx))
static unsigned short normal_i2c[] = { 0x2e, I2C_CLIENT_END };
I2C_CLIENT_INSMOD_1(adt7475);
static const struct i2c_device_id adt7475_id[] = {
{ "adt7475", adt7475 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adt7475_id);
struct adt7475_data {
struct device *hwmon_dev;
struct mutex lock;
unsigned long measure_updated;
unsigned long limits_updated;
char valid;
u8 config5;
u16 alarms;
u16 voltage[3][3];
u16 temp[7][3];
u16 tach[2][4];
u8 pwm[4][3];
u8 range[3];
u8 pwmctl[3];
u8 pwmchan[3];
};
static struct i2c_driver adt7475_driver;
static struct adt7475_data *adt7475_update_device(struct device *dev);
static void adt7475_read_hystersis(struct i2c_client *client);
static void adt7475_read_pwm(struct i2c_client *client, int index);
/* Given a temp value, convert it to register value */
static inline u16 temp2reg(struct adt7475_data *data, long val)
{
u16 ret;
if (!(data->config5 & CONFIG5_TWOSCOMP)) {
val = SENSORS_LIMIT(val, -64000, 191000);
ret = (val + 64500) / 1000;
} else {
val = SENSORS_LIMIT(val, -128000, 127000);
if (val < -500)
ret = (256500 + val) / 1000;
else
ret = (val + 500) / 1000;
}