Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754486Ab1DFDG1 (ORCPT ); Tue, 5 Apr 2011 23:06:27 -0400 Received: from hqemgate04.nvidia.com ([216.228.121.35]:9969 "EHLO hqemgate04.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753558Ab1DFDGY (ORCPT ); Tue, 5 Apr 2011 23:06:24 -0400 X-PGP-Universal: processed; by hqnvupgp08.nvidia.com on Tue, 05 Apr 2011 20:06:15 -0700 From: To: , CC: , , , , , , Andrew Chew Subject: [PATCH 1/1] [hwmon] nct1008: Initial NCT1008 driver Date: Tue, 5 Apr 2011 20:06:23 -0700 Message-ID: <1302059183-29631-1-git-send-email-achew@nvidia.com> X-Mailer: git-send-email 1.7.0.4 MIME-Version: 1.0 Content-Type: text/plain Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 12988 Lines: 483 From: Andrew Chew This is a digital temperature sensor I2C peripheral from ON Semiconductor. Signed-off-by: Andrew Chew --- drivers/hwmon/Kconfig | 11 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/nct1008.c | 423 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 435 insertions(+), 0 deletions(-) create mode 100644 drivers/hwmon/nct1008.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 060ef63..daa6159 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -738,6 +738,17 @@ config SENSORS_MAX6650 This driver can also be built as a module. If so, the module will be called max6650. +config SENSORS_NCT1008 + tristate "ON Semiconductor Temperature Sensor" + default n + depends on I2C + help + Say yes here if you wish to include the ON Semiconductor + NCT1008 Temperature sensor. + + This driver can also be built as a module. If so, the module + will be called nct1008. + config SENSORS_PC87360 tristate "National Semiconductor PC87360 family" select HWMON_VID diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 967d0ea..803893c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX6639) += max6639.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o +obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o diff --git a/drivers/hwmon/nct1008.c b/drivers/hwmon/nct1008.c new file mode 100644 index 0000000..ebae3b6 --- /dev/null +++ b/drivers/hwmon/nct1008.c @@ -0,0 +1,423 @@ +/* + * 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 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DRIVER_NAME "nct1008" + +/* Register Addresses */ +#define LOCAL_TEMP_RD 0x00 +#define EXT_HI_TEMP_RD 0x01 +#define EXT_LO_TEMP_RD 0x10 +#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) + +static inline u8 temperature_to_value(bool extended, u8 temp); + +struct nct1008_data { + struct device *hwmon_dev; + struct work_struct work; + struct i2c_client *client; + struct mutex mutex; + u8 config; + void (*alarm_fn)(bool raised); +}; + +static ssize_t nct1008_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + signed int temp_value = 0; + u8 data = 0; + + if (!dev || !buf || !attr) + return -EINVAL; + + data = i2c_smbus_read_byte_data(client, LOCAL_TEMP_RD); + if (data < 0) { + dev_err(&client->dev, "%s: failed to read " + "temperature\n", __func__); + return -EINVAL; + } + + temp_value = (signed int)data; + return sprintf(buf, "%d\n", temp_value); +} + +static ssize_t nct1008_show_ext_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + signed int temp_value = 0; + u8 data = 0; + + if (!dev || !buf || !attr) + return -EINVAL; + + data = i2c_smbus_read_byte_data(client, EXT_HI_TEMP_RD); + if (data < 0) { + dev_err(&client->dev, "%s: failed to read " + "ext_temperature\n", __func__); + return -EINVAL; + } + + temp_value = (signed int)data; + + data = i2c_smbus_read_byte_data(client, EXT_LO_TEMP_RD); + + return sprintf(buf, "%d.%d\n", temp_value, (25 * (data >> 6))); +} + +static ssize_t nct1008_store_throttle_ext_limit(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct nct1008_platform_data *pdata = client->dev.platform_data; + unsigned long res; + u8 ext_limit; + u8 value; + int err; + + err = strict_strtoul(buf, 0, &res); + if (err) + return -EINVAL; + + ext_limit = (u8)res; + + /* External Temperature Throttling limit */ + value = temperature_to_value(pdata->ext_range, ext_limit); + err = i2c_smbus_write_byte_data(client, + EXT_TEMP_HI_LIMIT_HI_BYTE, value); + if (err < 0) + return -EINVAL; + + pdata->throttling_ext_limit = ext_limit; + return count; +} + +static ssize_t nct1008_show_throttle_ext_limit(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; + u8 ext_limit = 0; + + if (!dev || !buf || !attr) + return -EINVAL; + + ext_limit = pdata->throttling_ext_limit; + + return sprintf(buf, "%d\n", ext_limit); +} + +static DEVICE_ATTR(temperature, S_IRUGO, nct1008_show_temp, NULL); +static DEVICE_ATTR(ext_temperature, S_IRUGO, nct1008_show_ext_temp, NULL); +static DEVICE_ATTR(throttle_ext_limit, S_IRUGO | S_IWUSR, + nct1008_show_throttle_ext_limit, + nct1008_store_throttle_ext_limit); + +static struct attribute *nct1008_attributes[] = { + &dev_attr_temperature.attr, + &dev_attr_ext_temperature.attr, + &dev_attr_throttle_ext_limit.attr, + NULL +}; + +static const struct attribute_group nct1008_attr_group = { + .attrs = nct1008_attributes, +}; + +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 void nct1008_disable(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 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))); + } + + 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; + + /* 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 __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 *data; + int err; + + data = kzalloc(sizeof(struct nct1008_data), GFP_KERNEL); + + if (!data) + return -ENOMEM; + + 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; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&client->dev, "%s: hwmon_device_register " + "failed\n", __func__); + goto error; + } + + /* register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &nct1008_attr_group); + if (err < 0) + goto fail_sys; + + nct1008_enable(client); /* sensor is running */ + + schedule_work(&data->work); /* check initial state */ + + return 0; + +fail_sys: + hwmon_device_unregister(data->hwmon_dev); +error: + kfree(data); + return err; +} + +static int __devexit nct1008_remove(struct i2c_client *client) +{ + struct nct1008_data *data = i2c_get_clientdata(client); + + free_irq(data->client->irq, data); + cancel_work_sync(&data->work); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &nct1008_attr_group); + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int nct1008_suspend(struct i2c_client *client, pm_message_t state) +{ + disable_irq(client->irq); + nct1008_disable(client); + + return 0; +} + +static int nct1008_resume(struct i2c_client *client) +{ + struct nct1008_data *data = i2c_get_clientdata(client); + + nct1008_enable(client); + enable_irq(client->irq); + schedule_work(&data->work); + + 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); -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/