blob: 8931c8a673e5753e48a35877ca100e07377a0636 [file] [log] [blame]
/*
* drivers/misc/nct1008.c
*
* Driver for NCT1008, temperature monitoring device from ON Semiconductors
*
* Copyright (c) 2010-2011, NVIDIA Corporation.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/nct1008.h>
#include <linux/delay.h>
#include <linux/regulator/consumer.h>
#define DRIVER_NAME "nct1008"
/* Register Addresses */
#define LOCAL_TEMP_RD 0x00
#define EXT_TEMP_RD_HI 0x01
#define EXT_TEMP_RD_LO 0x10
#define STATUS_RD 0x02
#define CONFIG_RD 0x03
#define LOCAL_TEMP_HI_LIMIT_RD 0x05
#define LOCAL_TEMP_LO_LIMIT_RD 0x06
#define EXT_TEMP_HI_LIMIT_HI_BYTE_RD 0x07
#define EXT_TEMP_LO_LIMIT_HI_BYTE_RD 0x08
#define CONFIG_WR 0x09
#define CONV_RATE_WR 0x0A
#define LOCAL_TEMP_HI_LIMIT_WR 0x0B
#define LOCAL_TEMP_LO_LIMIT_WR 0x0C
#define EXT_TEMP_HI_LIMIT_HI_BYTE_WR 0x0D
#define EXT_TEMP_LO_LIMIT_HI_BYTE_WR 0x0E
#define ONE_SHOT 0x0F
#define OFFSET_WR 0x11
#define OFFSET_QUARTER_WR 0x12
#define EXT_THERM_LIMIT_WR 0x19
#define LOCAL_THERM_LIMIT_WR 0x20
#define THERM_HYSTERESIS_WR 0x21
/* Configuration Register Bits */
#define EXTENDED_RANGE_BIT BIT(2)
#define THERM2_BIT BIT(5)
#define STANDBY_BIT BIT(6)
#define ALERT_BIT BIT(7)
/* Max Temperature Measurements */
#define EXTENDED_RANGE_OFFSET 64U
#define STANDARD_RANGE_MAX 127U
#define EXTENDED_RANGE_MAX (150U + EXTENDED_RANGE_OFFSET)
#define NCT1008_MIN_TEMP -64
#define NCT1008_MAX_TEMP 191
#define MAX_STR_PRINT 50
#define MAX_CONV_TIME_ONESHOT_MS (52)
#define CELSIUS_TO_MILLICELSIUS(x) ((x)*1000)
#define MILLICELSIUS_TO_CELSIUS(x) ((x)/1000)
static int conv_period_ms_table[] =
{16000, 8000, 4000, 2000, 1000, 500, 250, 125, 63, 32, 16};
static inline s8 value_to_temperature(bool extended, u8 value)
{
return extended ? (s8)(value - EXTENDED_RANGE_OFFSET) : (s8)value;
}
static inline u8 temperature_to_value(bool extended, s8 temp)
{
return extended ? (u8)(temp + EXTENDED_RANGE_OFFSET) : (u8)temp;
}
static int nct1008_get_temp(struct device *dev, long *pTemp)
{
struct i2c_client *client = to_i2c_client(dev);
struct nct1008_platform_data *pdata = client->dev.platform_data;
s8 temp_local;
u8 temp_ext_lo;
s8 temp_ext_hi;
long temp_ext_milli;
long temp_local_milli;
u8 value;
/* Read Local Temp */
value = i2c_smbus_read_byte_data(client, LOCAL_TEMP_RD);
if (value < 0)
goto error;
temp_local = value_to_temperature(pdata->ext_range, value);
temp_local_milli = CELSIUS_TO_MILLICELSIUS(temp_local);
/* Read External Temp */
value = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_LO);
if (value < 0)
goto error;
temp_ext_lo = (value >> 6);
value = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_HI);
if (value < 0)
goto error;
temp_ext_hi = value_to_temperature(pdata->ext_range, value);
temp_ext_milli = CELSIUS_TO_MILLICELSIUS(temp_ext_hi) +
temp_ext_lo * 250;
/* Return max between Local and External Temp */
*pTemp = max(temp_local_milli, temp_ext_milli);
dev_dbg(dev, "\n %s: ret temp=%ldC ", __func__, *pTemp);
return 0;
error:
dev_err(&client->dev, "\n error in file=: %s %s() line=%d: "
"error=%d ", __FILE__, __func__, __LINE__, value);
return value;
}
static ssize_t nct1008_show_temp(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct nct1008_platform_data *pdata = client->dev.platform_data;
s8 temp1 = 0;
s8 temp = 0;
u8 temp2 = 0;
int value = 0;
if (!dev || !buf || !attr)
return -EINVAL;
value = i2c_smbus_read_byte_data(client, LOCAL_TEMP_RD);
if (value < 0)
goto error;
temp1 = value_to_temperature(pdata->ext_range, value);
value = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_LO);
if (value < 0)
goto error;
temp2 = (value >> 6);
value = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_HI);
if (value < 0)
goto error;
temp = value_to_temperature(pdata->ext_range, value);
return snprintf(buf, MAX_STR_PRINT, "%d %d.%d\n",
temp1, temp, temp2 * 25);
error:
return snprintf(buf, MAX_STR_PRINT,
"Error read local/ext temperature\n");
}
static ssize_t nct1008_show_temp_overheat(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct nct1008_platform_data *pdata = client->dev.platform_data;
int value;
s8 temp, temp2;
/* Local temperature h/w shutdown limit */
value = i2c_smbus_read_byte_data(client, LOCAL_THERM_LIMIT_WR);
if (value < 0)
goto error;
temp = value_to_temperature(pdata->ext_range, value);
/* External temperature h/w shutdown limit */
value = i2c_smbus_read_byte_data(client, EXT_THERM_LIMIT_WR);
if (value < 0)
goto error;
temp2 = value_to_temperature(pdata->ext_range, value);
return snprintf(buf, MAX_STR_PRINT, "%d %d\n", temp, temp2);
error:
dev_err(dev, "%s: failed to read temperature-overheat "
"\n", __func__);
return snprintf(buf, MAX_STR_PRINT, " Rd overheat Error\n");
}
static ssize_t nct1008_set_temp_overheat(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long int num;
int err;
u8 temp;
long currTemp;
struct i2c_client *client = to_i2c_client(dev);
struct nct1008_platform_data *pdata = client->dev.platform_data;
char bufTemp[MAX_STR_PRINT];
char bufOverheat[MAX_STR_PRINT];
unsigned int ret;
if (strict_strtol(buf, 0, &num)) {
dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__,
__LINE__, __func__);
return -EINVAL;
}
if (((int)num < NCT1008_MIN_TEMP) || ((int)num >= NCT1008_MAX_TEMP)) {
dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__,
__LINE__, __func__);
return -EINVAL;
}
/* check for system power down */
err = nct1008_get_temp(dev, &currTemp);
if (err)
goto error;
currTemp = MILLICELSIUS_TO_CELSIUS(currTemp);
if (currTemp >= (int)num) {
ret = nct1008_show_temp(dev, attr, bufTemp);
ret = nct1008_show_temp_overheat(dev, attr, bufOverheat);
dev_err(dev, "\nCurrent temp: %s ", bufTemp);
dev_err(dev, "\nOld overheat limit: %s ", bufOverheat);
dev_err(dev, "\nReset from overheat: curr temp=%ld, "
"new overheat temp=%d\n\n", currTemp, (int)num);
}
/* External temperature h/w shutdown limit */
temp = temperature_to_value(pdata->ext_range, (s8)num);
err = i2c_smbus_write_byte_data(client, EXT_THERM_LIMIT_WR, temp);
if (err < 0)
goto error;
/* Local temperature h/w shutdown limit */
temp = temperature_to_value(pdata->ext_range, (s8)num);
err = i2c_smbus_write_byte_data(client, LOCAL_THERM_LIMIT_WR, temp);
if (err < 0)
goto error;
return count;
error:
dev_err(dev, " %s: failed to set temperature-overheat\n", __func__);
return err;
}
static ssize_t nct1008_show_temp_alert(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct nct1008_platform_data *pdata = client->dev.platform_data;
int value;
s8 temp_hi, temp_lo;
/* External Temperature Throttling hi-limit */
value = i2c_smbus_read_byte_data(client, EXT_TEMP_HI_LIMIT_HI_BYTE_RD);
if (value < 0)
goto error;
temp_hi = value_to_temperature(pdata->ext_range, value);
/* External Temperature Throttling lo-limit */
value = i2c_smbus_read_byte_data(client, EXT_TEMP_LO_LIMIT_HI_BYTE_RD);
if (value < 0)
goto error;
temp_lo = value_to_temperature(pdata->ext_range, value);
return snprintf(buf, MAX_STR_PRINT, "lo:%d hi:%d\n", temp_lo, temp_hi);
error:
dev_err(dev, "%s: failed to read temperature-alert\n", __func__);
return snprintf(buf, MAX_STR_PRINT, " Rd alert Error\n");
}
static ssize_t nct1008_set_temp_alert(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long int num;
int value;
int err;
struct i2c_client *client = to_i2c_client(dev);
struct nct1008_platform_data *pdata = client->dev.platform_data;
if (strict_strtol(buf, 0, &num)) {
dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__,
__LINE__, __func__);
return -EINVAL;
}
if (((int)num < NCT1008_MIN_TEMP) || ((int)num >= NCT1008_MAX_TEMP)) {
dev_err(dev, "\n file: %s, line=%d return %s() ", __FILE__,
__LINE__, __func__);
return -EINVAL;
}
/* External Temperature Throttling limit */
value = temperature_to_value(pdata->ext_range, (s8)num);
err = i2c_smbus_write_byte_data(client, EXT_TEMP_HI_LIMIT_HI_BYTE_WR,
value);
if (err < 0)
goto error;
/* Local Temperature Throttling limit */
err = i2c_smbus_write_byte_data(client, LOCAL_TEMP_HI_LIMIT_WR,
value);
if (err < 0)
goto error;
return count;
error:
dev_err(dev, "%s: failed to set temperature-alert "
"\n", __func__);
return err;
}
static ssize_t nct1008_show_ext_temp(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct nct1008_platform_data *pdata = client->dev.platform_data;
s8 temp_value;
int data = 0;
int data_lo;
if (!dev || !buf || !attr)
return -EINVAL;
/* When reading the full external temperature value, read the
* LSB first. This causes the MSB to be locked (that is, the
* ADC does not write to it) until it is read */
data_lo = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_LO);
if (data_lo < 0) {
dev_err(&client->dev, "%s: failed to read "
"ext_temperature, i2c error=%d\n", __func__, data_lo);
goto error;
}
data = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_HI);
if (data < 0) {
dev_err(&client->dev, "%s: failed to read "
"ext_temperature, i2c error=%d\n", __func__, data);
goto error;
}
temp_value = value_to_temperature(pdata->ext_range, data);
return snprintf(buf, MAX_STR_PRINT, "%d.%d\n", temp_value,
(25 * (data_lo >> 6)));
error:
return snprintf(buf, MAX_STR_PRINT, "Error read ext temperature\n");
}
static DEVICE_ATTR(temperature, S_IRUGO, nct1008_show_temp, NULL);
static DEVICE_ATTR(temperature_overheat, (S_IRUGO | (S_IWUSR | S_IWGRP)),
nct1008_show_temp_overheat, nct1008_set_temp_overheat);
static DEVICE_ATTR(temperature_alert, (S_IRUGO | (S_IWUSR | S_IWGRP)),
nct1008_show_temp_alert, nct1008_set_temp_alert);
static DEVICE_ATTR(ext_temperature, S_IRUGO, nct1008_show_ext_temp, NULL);
static struct attribute *nct1008_attributes[] = {
&dev_attr_temperature.attr,
&dev_attr_temperature_overheat.attr,
&dev_attr_temperature_alert.attr,
&dev_attr_ext_temperature.attr,
NULL
};
static const struct attribute_group nct1008_attr_group = {
.attrs = nct1008_attributes,
};
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>
static void print_reg(const char *reg_name, struct seq_file *s,
int offset)
{
struct nct1008_data *nct_data = s->private;
int ret;
ret = i2c_smbus_read_byte_data(nct_data->client,
offset);
if (ret >= 0)
seq_printf(s, "Reg %s Addr = 0x%02x Reg 0x%02x "
"Value 0x%02x\n", reg_name,
nct_data->client->addr,
offset, ret);
else
seq_printf(s, "%s: line=%d, i2c read error=%d\n",
__func__, __LINE__, ret);
}
static int dbg_nct1008_show(struct seq_file *s, void *unused)
{
seq_printf(s, "nct1008 Registers\n");
seq_printf(s, "------------------\n");
print_reg("Local Temp Value ", s, 0x00);
print_reg("Ext Temp Value Hi ", s, 0x01);
print_reg("Status ", s, 0x02);
print_reg("Configuration ", s, 0x03);
print_reg("Conversion Rate ", s, 0x04);
print_reg("Local Temp Hi Limit ", s, 0x05);
print_reg("Local Temp Lo Limit ", s, 0x06);
print_reg("Ext Temp Hi Limit Hi", s, 0x07);
print_reg("Ext Temp Hi Limit Lo", s, 0x13);
print_reg("Ext Temp Lo Limit Hi", s, 0x08);
print_reg("Ext Temp Lo Limit Lo", s, 0x14);
print_reg("Ext Temp Value Lo ", s, 0x10);
print_reg("Ext Temp Offset Hi ", s, 0x11);
print_reg("Ext Temp Offset Lo ", s, 0x12);
print_reg("Ext THERM Limit ", s, 0x19);
print_reg("Local THERM Limit ", s, 0x20);
print_reg("THERM Hysteresis ", s, 0x21);
print_reg("Consecutive ALERT ", s, 0x22);
return 0;
}
static int dbg_nct1008_open(struct inode *inode, struct file *file)
{
return single_open(file, dbg_nct1008_show, inode->i_private);
}
static const struct file_operations debug_fops = {
.open = dbg_nct1008_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init nct1008_debuginit(struct nct1008_data *nct)
{
int err = 0;
struct dentry *d;
d = debugfs_create_file("nct1008", S_IRUGO, NULL,
(void *)nct, &debug_fops);
if ((!d) || IS_ERR(d)) {
dev_err(&nct->client->dev, "Error: %s debugfs_create_file"
" returned an error\n", __func__);
err = -ENOENT;
goto end;
}
if (d == ERR_PTR(-ENODEV)) {
dev_err(&nct->client->dev, "Error: %s debugfs not supported "
"error=-ENODEV\n", __func__);
err = -ENODEV;
} else {
nct->dent = d;
}
end:
return err;
}
#else
static int __init nct1008_debuginit(struct nct1008_data *nct)
{
return 0;
}
#endif
static int nct1008_enable(struct i2c_client *client)
{
struct nct1008_data *data = i2c_get_clientdata(client);
int err;
err = i2c_smbus_write_byte_data(client, CONFIG_WR,
data->config & ~STANDBY_BIT);
if (err < 0)
dev_err(&client->dev, "%s, line=%d, i2c write error=%d\n",
__func__, __LINE__, err);
return err;
}
static int nct1008_disable(struct i2c_client *client)
{
struct nct1008_data *data = i2c_get_clientdata(client);
int err;
err = i2c_smbus_write_byte_data(client, CONFIG_WR,
data->config | STANDBY_BIT);
if (err < 0)
dev_err(&client->dev, "%s, line=%d, i2c write error=%d\n",
__func__, __LINE__, err);
return err;
}
static int nct1008_within_limits(struct nct1008_data *data)
{
int intr_status;
intr_status = i2c_smbus_read_byte_data(data->client, STATUS_RD);
return !(intr_status & (BIT(3) | BIT(4)));
}
static void nct1008_work_func(struct work_struct *work)
{
struct nct1008_data *data = container_of(work, struct nct1008_data,
work);
int intr_status;
struct timespec ts;
nct1008_disable(data->client);
if (data->alert_func)
if (!nct1008_within_limits(data))
data->alert_func(data->alert_data);
/* Initiate one-shot conversion */
i2c_smbus_write_byte_data(data->client, ONE_SHOT, 0x1);
/* Give hardware necessary time to finish conversion */
ts = ns_to_timespec(MAX_CONV_TIME_ONESHOT_MS * 1000 * 1000);
hrtimer_nanosleep(&ts, NULL, HRTIMER_MODE_REL, CLOCK_MONOTONIC);
intr_status = i2c_smbus_read_byte_data(data->client, STATUS_RD);
nct1008_enable(data->client);
enable_irq(data->client->irq);
}
static irqreturn_t nct1008_irq(int irq, void *dev_id)
{
struct nct1008_data *data = dev_id;
disable_irq_nosync(irq);
queue_work(data->workqueue, &data->work);
return IRQ_HANDLED;
}
static void nct1008_power_control(struct nct1008_data *data, bool is_enable)
{
int ret;
if (!data->nct_reg) {
data->nct_reg = regulator_get(&data->client->dev, "vdd");
if (IS_ERR_OR_NULL(data->nct_reg)) {
if (PTR_ERR(data->nct_reg) == -ENODEV)
dev_info(&data->client->dev,
"no regulator found for vdd."
" Assuming vdd is always powered");
else
dev_warn(&data->client->dev, "Error [%ld] in "
"getting the regulator handle for"
" vdd\n", PTR_ERR(data->nct_reg));
data->nct_reg = NULL;
return;
}
}
if (is_enable)
ret = regulator_enable(data->nct_reg);
else
ret = regulator_disable(data->nct_reg);
if (ret < 0)
dev_err(&data->client->dev, "Error in %s rail vdd_nct1008, "
"error %d\n", (is_enable) ? "enabling" : "disabling",
ret);
else
dev_info(&data->client->dev, "success in %s rail vdd_nct1008\n",
(is_enable) ? "enabling" : "disabling");
}
static int __devinit nct1008_configure_sensor(struct nct1008_data* data)
{
struct i2c_client *client = data->client;
struct nct1008_platform_data *pdata = client->dev.platform_data;
u8 value;
s8 temp;
u8 temp2;
int err;
if (!pdata || !pdata->supported_hwrev)
return -ENODEV;
/* Place in Standby */
data->config = STANDBY_BIT;
err = i2c_smbus_write_byte_data(client, CONFIG_WR, data->config);
if (err)
goto error;
/* External temperature h/w shutdown limit */
value = temperature_to_value(pdata->ext_range, NCT1008_MAX_TEMP);
err = i2c_smbus_write_byte_data(client, EXT_THERM_LIMIT_WR, value);
if (err)
goto error;
/* Local temperature h/w shutdown limit */
value = temperature_to_value(pdata->ext_range, NCT1008_MAX_TEMP);
err = i2c_smbus_write_byte_data(client, LOCAL_THERM_LIMIT_WR, value);
if (err)
goto error;
/* set extended range mode if needed */
if (pdata->ext_range)
data->config |= EXTENDED_RANGE_BIT;
data->config &= ~(THERM2_BIT | ALERT_BIT);
err = i2c_smbus_write_byte_data(client, CONFIG_WR, data->config);
if (err)
goto error;
/* Temperature conversion rate */
err = i2c_smbus_write_byte_data(client, CONV_RATE_WR, pdata->conv_rate);
if (err)
goto error;
data->conv_period_ms = conv_period_ms_table[pdata->conv_rate];
/* Setup local hi and lo limits */
err = i2c_smbus_write_byte_data(client,
LOCAL_TEMP_HI_LIMIT_WR, NCT1008_MAX_TEMP);
if (err)
goto error;
err = i2c_smbus_write_byte_data(client,
LOCAL_TEMP_LO_LIMIT_WR, 0);
if (err)
goto error;
/* Setup external hi and lo limits */
err = i2c_smbus_write_byte_data(client,
EXT_TEMP_LO_LIMIT_HI_BYTE_WR, 0);
if (err)
goto error;
err = i2c_smbus_write_byte_data(client, EXT_TEMP_HI_LIMIT_HI_BYTE_WR,
NCT1008_MAX_TEMP);
if (err)
goto error;
/* read initial temperature */
value = i2c_smbus_read_byte_data(client, LOCAL_TEMP_RD);
if (value < 0) {
err = value;
goto error;
}
temp = value_to_temperature(pdata->ext_range, value);
dev_dbg(&client->dev, "\n initial local temp = %d ", temp);
value = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_LO);
if (value < 0) {
err = value;
goto error;
}
temp2 = (value >> 6);
value = i2c_smbus_read_byte_data(client, EXT_TEMP_RD_HI);
if (value < 0) {
err = value;
goto error;
}
temp = value_to_temperature(pdata->ext_range, value);
if (temp2 > 0)
dev_dbg(&client->dev, "\n initial ext temp = %d.%d deg",
temp, temp2 * 25);
else
dev_dbg(&client->dev, "\n initial ext temp = %d.0 deg", temp);
/* Remote channel offset */
err = i2c_smbus_write_byte_data(client, OFFSET_WR, pdata->offset / 4);
if (err < 0)
goto error;
/* Remote channel offset fraction (quarters) */
err = i2c_smbus_write_byte_data(client, OFFSET_QUARTER_WR,
(pdata->offset % 4) << 6);
if (err < 0)
goto error;
/* register sysfs hooks */
err = sysfs_create_group(&client->dev.kobj, &nct1008_attr_group);
if (err < 0) {
dev_err(&client->dev, "\n sysfs create err=%d ", err);
goto error;
}
return 0;
error:
dev_err(&client->dev, "\n exit %s, err=%d ", __func__, err);
return err;
}
static int __devinit nct1008_configure_irq(struct nct1008_data *data)
{
data->workqueue = create_singlethread_workqueue("nct1008");
INIT_WORK(&data->work, nct1008_work_func);
if (data->client->irq < 0)
return 0;
else
return request_irq(data->client->irq, nct1008_irq,
IRQF_TRIGGER_LOW,
DRIVER_NAME, data);
}
int nct1008_thermal_get_temp(struct nct1008_data *data, long *temp)
{
return nct1008_get_temp(&data->client->dev, temp);
}
int nct1008_thermal_get_temp_low(struct nct1008_data *data, long *temp)
{
*temp = 0;
return 0;
}
int nct1008_thermal_set_limits(struct nct1008_data *data,
long lo_limit_milli,
long hi_limit_milli)
{
int err;
u8 value;
bool extended_range = data->plat_data.ext_range;
long lo_limit = MILLICELSIUS_TO_CELSIUS(lo_limit_milli);
long hi_limit = MILLICELSIUS_TO_CELSIUS(hi_limit_milli);
if (lo_limit >= hi_limit)
return -EINVAL;
if (data->current_lo_limit != lo_limit) {
value = temperature_to_value(extended_range, lo_limit);
pr_debug("%s: set lo_limit %ld\n", __func__, lo_limit);
err = i2c_smbus_write_byte_data(data->client,
EXT_TEMP_LO_LIMIT_HI_BYTE_WR, value);
if (err)
return err;
data->current_lo_limit = lo_limit;
}
if (data->current_hi_limit != hi_limit) {
value = temperature_to_value(extended_range, hi_limit);
pr_debug("%s: set hi_limit %ld\n", __func__, hi_limit);
err = i2c_smbus_write_byte_data(data->client,
EXT_TEMP_HI_LIMIT_HI_BYTE_WR, value);
if (err)
return err;
data->current_hi_limit = hi_limit;
}
return 0;
}
int nct1008_thermal_set_alert(struct nct1008_data *data,
void (*alert_func)(void *),
void *alert_data)
{
data->alert_data = alert_data;
data->alert_func = alert_func;
return 0;
}
int nct1008_thermal_set_shutdown_temp(struct nct1008_data *data,
long shutdown_temp_milli)
{
struct i2c_client *client = data->client;
struct nct1008_platform_data *pdata = client->dev.platform_data;
int err;
u8 value;
long shutdown_temp;
shutdown_temp = MILLICELSIUS_TO_CELSIUS(shutdown_temp_milli);
/* External temperature h/w shutdown limit */
value = temperature_to_value(pdata->ext_range, shutdown_temp);
err = i2c_smbus_write_byte_data(client, EXT_THERM_LIMIT_WR, value);
if (err)
return err;
/* Local temperature h/w shutdown limit */
value = temperature_to_value(pdata->ext_range, shutdown_temp);
err = i2c_smbus_write_byte_data(client, LOCAL_THERM_LIMIT_WR, value);
if (err)
return err;
return 0;
}
/*
* Manufacturer(OnSemi) recommended sequence for
* Extended Range mode is as follows
* 1. Place in Standby
* 2. Scale the THERM and ALERT limits
* appropriately(for Extended Range mode).
* 3. Enable Extended Range mode.
* ALERT mask/THERM2 mode may be done here
* as these are not critical
* 4. Set Conversion Rate as required
* 5. Take device out of Standby
*/
/*
* function nct1008_probe takes care of initial configuration
*/
static int __devinit nct1008_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct nct1008_data *data;
int err;
unsigned int delay;
data = kzalloc(sizeof(struct nct1008_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
memcpy(&data->plat_data, client->dev.platform_data,
sizeof(struct nct1008_platform_data));
i2c_set_clientdata(client, data);
nct1008_power_control(data, true);
/* extended range recommended steps 1 through 4 taken care
* in nct1008_configure_sensor function */
err = nct1008_configure_sensor(data); /* sensor is in standby */
if (err < 0) {
dev_err(&client->dev, "\n error file: %s : %s(), line=%d ",
__FILE__, __func__, __LINE__);
goto error;
}
err = nct1008_configure_irq(data);
if (err < 0) {
dev_err(&client->dev, "\n error file: %s : %s(), line=%d ",
__FILE__, __func__, __LINE__);
goto error;
}
dev_info(&client->dev, "%s: initialized\n", __func__);
/* extended range recommended step 5 is in nct1008_enable function */
err = nct1008_enable(client); /* sensor is running */
if (err < 0) {
dev_err(&client->dev, "Error: %s, line=%d, error=%d\n",
__func__, __LINE__, err);
goto error;
}
err = nct1008_debuginit(data);
if (err < 0)
err = 0; /* without debugfs we may continue */
/* notify callback that probe is done */
if (data->plat_data.probe_callback)
data->plat_data.probe_callback(data);
return 0;
error:
dev_err(&client->dev, "\n exit %s, err=%d ", __func__, err);
nct1008_power_control(data, false);
if (data->nct_reg)
regulator_put(data->nct_reg);
kfree(data);
return err;
}
static int __devexit nct1008_remove(struct i2c_client *client)
{
struct nct1008_data *data = i2c_get_clientdata(client);
if (data->dent)
debugfs_remove(data->dent);
free_irq(data->client->irq, data);
cancel_work_sync(&data->work);
sysfs_remove_group(&client->dev.kobj, &nct1008_attr_group);
nct1008_power_control(data, false);
if (data->nct_reg)
regulator_put(data->nct_reg);
kfree(data);
return 0;
}
#ifdef CONFIG_PM
static int nct1008_suspend(struct i2c_client *client, pm_message_t state)
{
int err;
disable_irq(client->irq);
err = nct1008_disable(client);
return err;
}
static int nct1008_resume(struct i2c_client *client)
{
struct nct1008_data *data = i2c_get_clientdata(client);
int err;
err = nct1008_enable(client);
if (err < 0) {
dev_err(&client->dev, "Error: %s, error=%d\n",
__func__, err);
return err;
}
enable_irq(client->irq);
return 0;
}
#endif
static const struct i2c_device_id nct1008_id[] = {
{ DRIVER_NAME, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, nct1008_id);
static struct i2c_driver nct1008_driver = {
.driver = {
.name = DRIVER_NAME,
},
.probe = nct1008_probe,
.remove = __devexit_p(nct1008_remove),
.id_table = nct1008_id,
#ifdef CONFIG_PM
.suspend = nct1008_suspend,
.resume = nct1008_resume,
#endif
};
static int __init nct1008_init(void)
{
return i2c_add_driver(&nct1008_driver);
}
static void __exit nct1008_exit(void)
{
i2c_del_driver(&nct1008_driver);
}
MODULE_DESCRIPTION("Temperature sensor driver for OnSemi NCT1008");
MODULE_LICENSE("GPL");
module_init(nct1008_init);
module_exit(nct1008_exit);