From 3d9f0d7a7eaf96a5ee00bb796604450fb736bf8c Mon Sep 17 00:00:00 2001 From: Todd Poynor Date: Mon, 20 Dec 2010 15:05:52 -0800 Subject: [PATCH] NCT1008 temperature sensor driver Replace the PM-only driver for NCT1008 with a new version written by Varun Wadekar and Dmitriy Gruzman. Add a callback to an alarm function specified in the board platform data. Change-Id: Ib429533930ee75af3402d24b0bc286da9f6ee67b Signed-off-by: Todd Poynor --- drivers/misc/nct1008.c | 300 ++++++++++++++++++++++++++++++---------- include/linux/nct1008.h | 74 ++++------ 2 files changed, 256 insertions(+), 118 deletions(-) diff --git a/drivers/misc/nct1008.c b/drivers/misc/nct1008.c index a29e984bc4c6..4ae48e4003aa 100755 --- a/drivers/misc/nct1008.c +++ b/drivers/misc/nct1008.c @@ -1,139 +1,291 @@ /* - * Copyright (C) 2010 Motorola, Inc. + * drivers/misc/nct1008.c + * + * Driver for NCT1008, temperature monitoring device from ON Semiconductors + * + * Copyright (c) 2010, NVIDIA Corporation. * * 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. + * 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. + * 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., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307, USA + * 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 -#include -#include + #include -#include -#include +#include +#include +#include #include -#include -#include +#include +#include + +#include -#define NCT_BIT_CONFIG_RUN_STOP 6 +#define DRIVER_NAME "nct1008" + +/* Register Addresses */ +#define LOCAL_TEMP_RD 0x00 +#define STATUS_RD 0x02 +#define CONFIG_RD 0x03 + +#define CONFIG_WR 0x09 +#define CONV_RATE_WR 0x0A +#define LOCAL_TEMP_HI_LIMIT_WR 0x0B +#define EXT_TEMP_HI_LIMIT_HI_BYTE 0x0D +#define OFFSET_WR 0x11 +#define EXT_THERM_LIMIT_WR 0x19 +#define LOCAL_THERM_LIMIT_WR 0x20 +#define THERM_HYSTERESIS_WR 0x21 + +/* Configuration Register Bits */ +#define EXTENDED_RANGE_BIT (0x1 << 2) +#define THERM2_BIT (0x1 << 5) +#define STANDBY_BIT (0x1 << 6) + +/* Max Temperature Measurements */ +#define EXTENDED_RANGE_OFFSET 64U +#define STANDARD_RANGE_MAX 127U +#define EXTENDED_RANGE_MAX (150U + EXTENDED_RANGE_OFFSET) struct nct1008_data { + struct work_struct work; struct i2c_client *client; + struct mutex mutex; + u8 config; + void (*alarm_fn)(bool raised); }; -static uint32_t nct1008_debug; -module_param_named(temp_debug, nct1008_debug, uint, 0664); +static void nct1008_enable(struct i2c_client *client) +{ + struct nct1008_data *data = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, CONFIG_WR, + data->config & ~STANDBY_BIT); +} -static int nct1008_run(struct nct1008_data *nct, u8 run) +static void nct1008_disable(struct i2c_client *client) { - int err; - u8 rd_val; - u8 wr_val; + struct nct1008_data *data = i2c_get_clientdata(client); - rd_val = i2c_smbus_read_byte_data(nct->client, NCT_CONFIG_RD); - if (rd_val < 0) { - pr_err("%s: config register read fail: %d\n", __func__, rd_val); - return rd_val; + i2c_smbus_write_byte_data(client, CONFIG_WR, + data->config | STANDBY_BIT); +} + + +static void nct1008_work_func(struct work_struct *work) +{ + struct nct1008_data *data = container_of(work, struct nct1008_data, work); + int irq = data->client->irq; + + mutex_lock(&data->mutex); + + if (data->alarm_fn) { + /* Therm2 line is active low */ + data->alarm_fn(!gpio_get_value(irq_to_gpio(irq))); } - if (nct1008_debug) - pr_info("%s: Previous config is 0x%02x\n", __func__, rd_val); + mutex_unlock(&data->mutex); +} + +static irqreturn_t nct1008_irq(int irq, void *dev_id) +{ + struct nct1008_data *data = dev_id; + schedule_work(&data->work); + + return IRQ_HANDLED; +} + +static inline u8 value_to_temperature(bool extended, u8 value) +{ + return (extended ? (u8)(value - EXTENDED_RANGE_OFFSET) : value); +} + +static inline u8 temperature_to_value(bool extended, u8 temp) +{ + return (extended ? (u8)(temp + EXTENDED_RANGE_OFFSET) : temp); +} + +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; + int err; + + if (!pdata || !pdata->supported_hwrev) + return -ENODEV; + + /* + * Initial Configuration - device is placed in standby and + * ALERT/THERM2 pin is configured as THERM2 + */ + data->config = value = pdata->ext_range ? + (STANDBY_BIT | THERM2_BIT | EXTENDED_RANGE_BIT) : + (STANDBY_BIT | THERM2_BIT); + + err = i2c_smbus_write_byte_data(client, CONFIG_WR, value); + if (err < 0) + goto error; + + /* Temperature conversion rate */ + err = i2c_smbus_write_byte_data(client, CONV_RATE_WR, pdata->conv_rate); + if (err < 0) + goto error; - if (run) - wr_val = rd_val & ~(1 << NCT_BIT_CONFIG_RUN_STOP); - else - wr_val = rd_val | (1 << NCT_BIT_CONFIG_RUN_STOP); - err = i2c_smbus_write_byte_data(nct->client, NCT_CONFIG_WR, wr_val); - if (err) - pr_err("%s: setting RUN/STOP failed: %d\n", __func__, err); + /* External temperature h/w shutdown limit */ + value = temperature_to_value(pdata->ext_range, pdata->shutdown_ext_limit); + err = i2c_smbus_write_byte_data(client, EXT_THERM_LIMIT_WR, value); + if (err < 0) + goto error; + /* Local temperature h/w shutdown limit */ + value = temperature_to_value(pdata->ext_range, pdata->shutdown_local_limit); + err = i2c_smbus_write_byte_data(client, LOCAL_THERM_LIMIT_WR, value); + if (err < 0) + goto error; + + /* External Temperature Throttling limit */ + value = temperature_to_value(pdata->ext_range, pdata->throttling_ext_limit); + err = i2c_smbus_write_byte_data(client, EXT_TEMP_HI_LIMIT_HI_BYTE, value); + if (err < 0) + goto error; + + /* Local Temperature Throttling limit */ + value = pdata->ext_range ? EXTENDED_RANGE_MAX : STANDARD_RANGE_MAX; + err = i2c_smbus_write_byte_data(client, LOCAL_TEMP_HI_LIMIT_WR, value); + if (err < 0) + goto error; + + /* Remote channel offset */ + err = i2c_smbus_write_byte_data(client, OFFSET_WR, pdata->offset); + if (err < 0) + goto error; + + /* THERM hysteresis */ + err = i2c_smbus_write_byte_data(client, THERM_HYSTERESIS_WR, pdata->hysteresis); + if (err < 0) + goto error; + + data->alarm_fn = pdata->alarm_fn; + return 0; +error: return err; } -static int nct1008_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int __devinit nct1008_configure_irq(struct nct1008_data *data) +{ + INIT_WORK(&data->work, nct1008_work_func); + + return request_irq(data->client->irq, nct1008_irq, IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, DRIVER_NAME, data); +} + +static int __devinit nct1008_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct nct1008_data *nct; + struct nct1008_data *data; + int err; + + data = kzalloc(sizeof(struct nct1008_data), GFP_KERNEL); - nct = kzalloc(sizeof(struct nct1008_data), GFP_KERNEL); - if (nct == NULL) + if (!data) return -ENOMEM; - nct->client = client; - i2c_set_clientdata(client, nct); + data->client = client; + i2c_set_clientdata(client, data); + mutex_init(&data->mutex); + + err = nct1008_configure_sensor(data); /* sensor is in standby */ + if (err < 0) + goto error; + + err = nct1008_configure_irq(data); + if (err < 0) + goto error; + + nct1008_enable(client); /* sensor is running */ + + schedule_work(&data->work); /* check initial state */ return 0; + +error: + kfree(data); + return err; } -static int nct1008_remove(struct i2c_client *client) +static int __devexit nct1008_remove(struct i2c_client *client) { - struct nct1008_data *nct = i2c_get_clientdata(client); + struct nct1008_data *data = i2c_get_clientdata(client); + + free_irq(data->client->irq, data); + cancel_work_sync(&data->work); + kfree(data); - kfree(nct); return 0; } -static int nct1008_suspend(struct i2c_client *client, pm_message_t mesg) +#ifdef CONFIG_PM +static int nct1008_suspend(struct i2c_client *client, pm_message_t state) { - struct nct1008_data *nct = i2c_get_clientdata(client); - - if (nct1008_debug) - pr_info("%s: Suspending\n", __func__); + disable_irq(client->irq); + nct1008_disable(client); - return nct1008_run(nct, 0); + return 0; } static int nct1008_resume(struct i2c_client *client) { - struct nct1008_data *nct = i2c_get_clientdata(client); + struct nct1008_data *data = i2c_get_clientdata(client); - if (nct1008_debug) - pr_info("%s: Resuming\n", __func__); + nct1008_enable(client); + enable_irq(client->irq); + schedule_work(&data->work); - return nct1008_run(nct, 1); + return 0; } +#endif static const struct i2c_device_id nct1008_id[] = { - {"nct1008", 0}, - {} + { DRIVER_NAME, 0 }, + { } }; +MODULE_DEVICE_TABLE(i2c, nct1008_id); -static struct i2c_driver nct1008_i2c_driver = { - .probe = nct1008_probe, - .remove = nct1008_remove, - .suspend = nct1008_suspend, - .resume = nct1008_resume, - .id_table = nct1008_id, +static struct i2c_driver nct1008_driver = { .driver = { - .name = "nct1008", - .owner = THIS_MODULE, + .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_i2c_driver); + return i2c_add_driver(&nct1008_driver); } static void __exit nct1008_exit(void) { - i2c_del_driver(&nct1008_i2c_driver); + i2c_del_driver(&nct1008_driver); } -module_init(nct1008_init); -module_exit(nct1008_exit); - MODULE_DESCRIPTION("Temperature sensor driver for OnSemi NCT1008"); -MODULE_AUTHOR("Motorola"); MODULE_LICENSE("GPL"); + +module_init (nct1008_init); +module_exit (nct1008_exit); diff --git a/include/linux/nct1008.h b/include/linux/nct1008.h index c6b396246659..d65693507299 100644 --- a/include/linux/nct1008.h +++ b/include/linux/nct1008.h @@ -1,54 +1,40 @@ /* - * Copyright (C) 2010 Motorola, Inc. + * include/linux/nct1008.h + * + * NCT1008, temperature monitoring device from ON Semiconductors + * + * Copyright (c) 2010, NVIDIA Corporation. * * 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. + * 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. + * 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., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307, USA + * 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. */ -#ifndef _LINUX_NCT1008_H__ -#define _LINUX_NCT1008_H__ - -/* NCT1008 Read Only Registers */ -#define NCT_LOCAL_TEMP_RD 0x00 -#define NCT_EXT_TEMP_HIGH_RD 0x01 -#define NCT_EXT_TEMP_LOW_RD 0x10 -#define NCT_STATUS_RD 0x02 - -/* NCT1008 Control Registers */ -#define NCT_CONFIG_RD 0x03 -#define NCT_CONFIG_WR 0x09 -#define NCT_CONV_RATE_RD 0x04 -#define NCT_CONV_RATE_WR 0x0A - -/* NCT1008 Limit Registers */ -#define NCT_LOCAL_TEMP_HIGH_LIMIT_RD 0x05 -#define NCT_LOCAL_TEMP_LOW_LIMIT_RD 0x06 -#define NCT_LOCAL_TEMP_HIGH_LIMIT_WR 0x0B -#define NCT_LOCAL_TEMP_LOW_LIMIT_WR 0x0C +#ifndef _LINUX_NCT1008_H +#define _LINUX_NCT1008_H -#define NCT_EXT_TEMP_HIGH_LIMIT_HBYTE_RD 0x07 -#define NCT_EXT_TEMP_LOW_LIMIT_HBYTE_RD 0x08 -#define NCT_EXT_TEMP_HIGH_LIMIT_HBYTE_WR 0x0D -#define NCT_EXT_TEMP_LOW_LIMIT_HBYTE_WR 0x0E -#define NCT_EXT_TEMP_HIGH_LIMIT_LBYTE_RDWR 0x13 -#define NCT_EXT_TEMP_LOW_LIMIT_LBYTE_RDWR 0x14 -#define NCT_EXT_THERM_LIMIT 0x19 -#define NCT_LOCAL_THERM_LIMIT 0x20 +#include -#define NCT_EXT_TEMP_OFFSET_HIGH_RDWR 0x11 -#define NCT_EXT_TEMP_OFFSET_LOW_RDWR 0x12 -#define NCT_THERM_HYST 0x21 -#define NCT_CONSEC_ALERT 0x22 +struct nct1008_platform_data { + bool supported_hwrev; + bool ext_range; + u8 conv_rate; + u8 offset; + u8 hysteresis; + u8 shutdown_ext_limit; + u8 shutdown_local_limit; + u8 throttling_ext_limit; + void (*alarm_fn)(bool raised); +}; -#endif /* _LINUX_NCT1008_H__ */ +#endif /* _LINUX_NCT1008_H */ -- 2.34.1