/*
* nct7802 - Driver for Nuvoton NCT7802Y
*
* Copyright (C) 2014 Guenter Roeck <linux@roeck-us.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define DRVNAME "nct7802"
static const u8 REG_VOLTAGE[5] = { 0x09, 0x0a, 0x0c, 0x0d, 0x0e };
static const u8 REG_VOLTAGE_LIMIT_LSB[2][5] = {
{ 0x40, 0x00, 0x42, 0x44, 0x46 },
{ 0x3f, 0x00, 0x41, 0x43, 0x45 },
};
static const u8 REG_VOLTAGE_LIMIT_MSB[5] = { 0x48, 0x00, 0x47, 0x47, 0x48 };
static const u8 REG_VOLTAGE_LIMIT_MSB_SHIFT[2][5] = {
{ 0, 0, 4, 0, 4 },
{ 2, 0, 6, 2, 6 },
};
#define REG_BANK 0x00
#define REG_TEMP_LSB 0x05
#define REG_TEMP_PECI_LSB 0x08
#define REG_VOLTAGE_LOW 0x0f
#define REG_FANCOUNT_LOW 0x13
#define REG_START 0x21
#define REG_MODE 0x22
#define REG_PECI_ENABLE 0x23
#define REG_FAN_ENABLE 0x24
#define REG_VMON_ENABLE 0x25
#define REG_VENDOR_ID 0xfd
#define REG_CHIP_ID 0xfe
#define REG_VERSION_ID 0xff
/*
* Data structures and manipulation thereof
*/
struct nct7802_data {
struct regmap *regmap;
struct mutex access_lock; /* for multi-byte read and write operations */
};
static int nct7802_read_temp(struct nct7802_data *data,
u8 reg_temp, u8 reg_temp_low, int *temp)
{
unsigned int t1, t2 = 0;
int err;
*temp = 0;
mutex_lock(&data->access_lock);
err = regmap_read(data->regmap, reg_temp, &t1);
if (err < 0)
goto abort;
t1 <<= 8;
if (reg_temp_low) { /* 11 bit data */
err = regmap_read(data->regmap, reg_temp_low, &t2);
if (err < 0)
goto abort;
}
t1 |= t2 & 0xe0;
*temp = (s16)t1 / 32 * 125;
abort:
mutex_unlock(&data->access_lock);
return err;
}
static int nct7802_read_fan(struct nct7802_data *data, u8 reg_fan)
{
unsigned int f1, f2;
int ret;
mutex_lock(&data->access_lock);
ret = regmap_read(data->regmap, reg_fan, &f1);
if (ret < 0)
goto abort;
ret = regmap_read(data->regmap, REG_FANCOUNT_LOW, &f2);
if (ret < 0)
goto abort;
ret = (f1 << 5) | (f2 >> 3);
/* convert fan count to rpm */
if (ret == 0x1fff) /* maximum value, assume fan is stopped */
ret = 0;
else if (ret)
ret = DIV_ROUND_CLOSEST(1350000U, ret);
abort:
mutex_unlock(&data->access_lock);
return ret;
}
static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low,
u8 reg_fan_high)
{
unsigned int f1, f2;
int ret;
mutex_lock(&data->access_lock);
ret = regmap_read(data->regmap, reg_fan_low, &f1);
if (ret < 0)
goto abort;
ret = regmap_read(data->regmap, reg_fan_high, &f2);
if (ret < 0)
goto abort;
ret = f1 | ((f2 & 0xf8) << 5);
/* convert fan count to rpm */
if (ret == 0x1fff) /* maximum value, assume no limit */
ret = 0;
else if (ret)
ret = DIV_ROUND_CLOSEST(1350000U, ret);
abort:
mutex_unlock(&data->access_lock);
return ret;
}
static int nct7802_write_fan_min(struct nct7802_data *data, u8 reg_fan_low,
u8 reg_fan_high, unsigned int limit)
{
int err;
if (limit)
limit = DIV_ROUND_CLOSEST(1350000U, limit);
else
limit = 0x1fff;
limit = clamp_val(limit, 0, 0x1fff);
mutex_lock(&data->access_lock);
err = regmap_write(data->regmap, reg_fan_low, limit & 0xff);
if (err < 0)
goto abort;
err = regmap_write(data->regmap, reg_fan_high, (limit & 0x1f00) >> 5);
abort:
mutex_unlock(&data->access_lock);
return err;
}
static u8 nct7802_vmul[] = { 4, 2, 2, 2, 2