[lm-sensors] [PATCH] max664x: New driver for Maxim MAX6646/6647/6649 sensor chips
Ben Hutchings
bhutchings at solarflare.com
Mon Jun 9 13:13:53 CEST 2008
Signed-off-by: Ben Hutchings <bhutchings at solarflare.com>
---
drivers/hwmon/Kconfig | 10 ++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/max664x.c | 355 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/max664x.h | 92 ++++++++++++
4 files changed, 458 insertions(+), 0 deletions(-)
create mode 100644 drivers/hwmon/max664x.c
create mode 100644 include/linux/max664x.h
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 00ff533..fad29c0 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -512,6 +512,16 @@ config SENSORS_MAX1619
This driver can also be built as a module. If so, the module
will be called max1619.
+config SENSORS_MAX664X
+ tristate "Maxim MAX6646/6647/6649 sensor chips"
+ depends on I2C
+ help
+ If you say yes here you get support for MAX6646/6647/6649
+ sensor chips.
+
+ This driver can also be built as a module. If so, the module
+ will be called max664x.
+
config SENSORS_MAX6650
tristate "Maxim MAX6650 sensor chip"
depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d098677..24b907e 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_LM92) += lm92.o
obj-$(CONFIG_SENSORS_LM93) += lm93.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
+obj-$(CONFIG_SENSORS_MAX664X) += max664x.o
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
diff --git a/drivers/hwmon/max664x.c b/drivers/hwmon/max664x.c
new file mode 100644
index 0000000..c5cd365
--- /dev/null
+++ b/drivers/hwmon/max664x.c
@@ -0,0 +1,355 @@
+/****************************************************************************
+ * Driver for Maxim MAX6646/6647/6649 sensor chips
+ * Copyright 2008 Solarflare Communications Inc.
+ *
+ * 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, incorporated herein by reference.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/max664x.h>
+
+/* Data sheet: <http://datasheets.maxim-ic.com/en/ds/MAX6646-MAX6649.pdf> */
+
+#define MAX664X_REG_RLTS 0x00
+#define MAX664X_REG_RRTE 0x01
+#define MAX664X_REG_RSL 0x02
+#define MAX664X_REG_RCL 0x03
+#define MAX664X_REG_RCRA 0x04
+#define MAX664X_REG_RLHN 0x05
+#define MAX664X_REG_RLLI 0x06
+#define MAX664X_REG_RRHI 0x07
+#define MAX664X_REG_RRLS 0x08
+#define MAX664X_REG_WCA 0x09
+#define MAX664X_REG_WCRW 0x0a
+#define MAX664X_REG_WLHO 0x0b
+#define MAX664X_REG_WLLM 0x0c
+#define MAX664X_REG_WRHA 0x0d
+#define MAX664X_REG_WRLN 0x0e
+#define MAX664X_REG_OSHT 0x0f
+#define MAX664X_REG_REET 0x10
+#define MAX664X_REG_RIET 0x11
+#define MAX664X_REG_RWOE 0x19
+#define MAX664X_REG_RWOI 0x20
+#define MAX664X_REG_HYS 0x21
+#define MAX664X_REG_QUEUE 0x22
+#define MAX664X_REG_MFID 0xfe
+#define MAX664X_REG_REVID 0xff
+
+struct max664x_data {
+ struct device *hwmon_dev;
+ struct mutex update_lock;
+ int valid;
+ unsigned long last_updated; /* in jiffies */
+ struct max664x_settings set;
+ struct max664x_values val;
+};
+
+#define TEMP_FROM_REG(val) ((val) * 1000)
+#define TEMP_TO_REG(val) ((val) / 1000)
+#define PERIOD_FROM_RATE_REG(val) (16000 >> min_t(int, val, 6))
+#define PERIOD_TO_RATE_REG(val) SENSORS_LIMIT(7 - ffs(val / 250), 0, 6)
+
+static struct max664x_data *max664x_update_device(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max664x_data *data = i2c_get_clientdata(client);
+
+ mutex_lock(&data->update_lock);
+
+ if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+ data->set.rate =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RCRA);
+ data->val.temp[0] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RLTS);
+ data->set.temp_overt[0] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RWOI);
+ data->set.temp_high_alert[0] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RLHN);
+ data->set.temp_low_alert[0] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RLLI);
+ data->val.temp[1] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RRTE);
+ data->set.temp_overt[1] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RWOE);
+ data->set.temp_high_alert[1] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RRHI);
+ data->set.temp_low_alert[1] =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_RRLS);
+ data->set.temp_overt_hyst =
+ i2c_smbus_read_byte_data(client, MAX664X_REG_HYS);
+
+ data->last_updated = jiffies;
+ data->valid = 1;
+ }
+
+ mutex_unlock(&data->update_lock);
+
+ return data;
+}
+
+struct max664x_values *max664x_update_values(struct i2c_client *client)
+{
+ struct max664x_data *data = max664x_update_device(&client->dev);
+ return &data->val;
+}
+EXPORT_SYMBOL(max664x_update_values);
+
+static void
+set_temp_limit(struct device *dev, const char *buf, int offset, int reg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max664x_data *data = i2c_get_clientdata(client);
+ long val = simple_strtol(buf, NULL, 10);
+ u8 reg_val = TEMP_TO_REG(val);
+
+ mutex_lock(&data->update_lock);
+ ((u8 *)&data->set)[offset] = reg_val;
+ i2c_smbus_write_byte_data(client, reg, reg_val);
+ mutex_unlock(&data->update_lock);
+}
+
+#define TEMP_LIMIT_ATTR(offset, limit, array_name, reg_name) \
+ static ssize_t \
+ show_temp##offset##_##limit(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ struct max664x_data *data = max664x_update_device(dev); \
+ return sprintf(buf, "%u\n", \
+ TEMP_FROM_REG(data->set.array_name[offset - 1])); \
+ } \
+ static ssize_t \
+ set_temp##offset##_##limit(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ set_temp_limit(dev, buf, \
+ offsetof(struct max664x_settings, \
+ array_name[offset - 1]), \
+ MAX664X_REG_##reg_name); \
+ return count; \
+ } \
+ static DEVICE_ATTR(temp##offset##_##limit, S_IRUGO | S_IWUSR, \
+ show_temp##offset##_##limit, \
+ set_temp##offset##_##limit);
+
+static ssize_t show_temp_crit_hyst(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max664x_data *data = max664x_update_device(dev);
+ return sprintf(buf, "%u\n", TEMP_FROM_REG(data->set.temp_overt_hyst));
+}
+
+static ssize_t
+set_temp_crit_hyst(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max664x_data *data = i2c_get_clientdata(client);
+ long val = simple_strtol(buf, NULL, 10);
+ u8 reg_val = TEMP_TO_REG(val);
+
+ mutex_lock(&data->update_lock);
+ data->set.temp_overt_hyst = reg_val;
+ i2c_smbus_write_byte_data(client, MAX664X_REG_HYS, reg_val);
+ mutex_unlock(&data->update_lock);
+ return count;
+}
+
+#define TEMP_ATTRS(offset, overt_reg, high_alert_reg, low_alert_reg) \
+ static ssize_t \
+ show_temp##offset##_input(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ struct max664x_data *data = max664x_update_device(dev); \
+ return sprintf(buf, "%u\n", \
+ TEMP_FROM_REG(data->val.temp[offset - 1])); \
+ } \
+ static DEVICE_ATTR(temp##offset##_input, S_IRUGO, \
+ show_temp##offset##_input, NULL); \
+ TEMP_LIMIT_ATTR(offset, crit, temp_overt, overt_reg) \
+ static DEVICE_ATTR(temp##offset##_crit_hyst, S_IRUGO, \
+ show_temp_crit_hyst, set_temp_crit_hyst); \
+ TEMP_LIMIT_ATTR(offset, max, temp_high_alert, high_alert_reg) \
+ TEMP_LIMIT_ATTR(offset, min, temp_low_alert, low_alert_reg)
+
+TEMP_ATTRS(1, RWOI, WLHO, WLLM)
+TEMP_ATTRS(2, RWOE, WRHA, WRLN)
+
+static ssize_t
+show_period(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct max664x_data *data = max664x_update_device(dev);
+ return sprintf(buf, "%u\n", PERIOD_FROM_RATE_REG(data->set.rate));
+}
+static ssize_t set_period(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max664x_data *data = i2c_get_clientdata(client);
+ long val = simple_strtol(buf, NULL, 10);
+ u8 reg_val = PERIOD_TO_RATE_REG(val);
+
+ mutex_lock(&data->update_lock);
+ data->set.rate = reg_val;
+ i2c_smbus_write_byte_data(client, MAX664X_REG_WCRW, reg_val);
+ mutex_unlock(&data->update_lock);
+ return count;
+}
+static DEVICE_ATTR(period, S_IRUGO | S_IWUSR, show_period, set_period);
+
+/*
+ * Status is read-to-clear, so it is not world-readable and is not
+ * updated with the other fields.
+ */
+static ssize_t show_status(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int val = i2c_smbus_read_byte_data(client, MAX664X_REG_RSL);
+ return val >= 0 ? sprintf(buf, "0x%x\n", val) : val;
+}
+static DEVICE_ATTR(status, S_IRUSR, show_status, NULL);
+
+int max664x_read_status(struct i2c_client *client)
+{
+ return i2c_smbus_read_byte_data(client, MAX664X_REG_RSL);
+}
+EXPORT_SYMBOL(max664x_read_status);
+
+static struct attribute *max664x_attributes[] = {
+ &dev_attr_temp1_input.attr,
+ &dev_attr_temp1_crit.attr,
+ &dev_attr_temp1_crit_hyst.attr,
+ &dev_attr_temp1_max.attr,
+ &dev_attr_temp1_min.attr,
+ &dev_attr_temp2_input.attr,
+ &dev_attr_temp2_crit.attr,
+ &dev_attr_temp2_crit_hyst.attr,
+ &dev_attr_temp2_max.attr,
+ &dev_attr_temp2_min.attr,
+
+ &dev_attr_period.attr,
+ &dev_attr_status.attr,
+
+ NULL
+};
+
+static const struct attribute_group max664x_group = {
+ .attrs = max664x_attributes,
+};
+
+static int
+max664x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ const struct max664x_platform_data *plat_data =
+ client->dev.platform_data;
+ struct max664x_data *data;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EIO;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->valid = 0;
+ mutex_init(&data->update_lock);
+ if (plat_data)
+ data->set = plat_data->set;
+ i2c_set_clientdata(client, data);
+
+ if (plat_data && plat_data->set_rate)
+ i2c_smbus_write_byte_data(client, MAX664X_REG_WCRW,
+ data->set.rate);
+ if (plat_data && plat_data->set_limits) {
+ i2c_smbus_write_byte_data(client, MAX664X_REG_RWOI,
+ data->set.temp_overt[0]);
+ i2c_smbus_write_byte_data(client, MAX664X_REG_WLHO,
+ data->set.temp_high_alert[0]);
+ i2c_smbus_write_byte_data(client, MAX664X_REG_WLLM,
+ data->set.temp_low_alert[0]);
+ i2c_smbus_write_byte_data(client, MAX664X_REG_RWOE,
+ data->set.temp_overt[1]);
+ i2c_smbus_write_byte_data(client, MAX664X_REG_WRHA,
+ data->set.temp_high_alert[1]);
+ i2c_smbus_write_byte_data(client, MAX664X_REG_WRLN,
+ data->set.temp_low_alert[1]);
+ i2c_smbus_write_byte_data(client, MAX664X_REG_HYS,
+ data->set.temp_overt_hyst);
+ }
+
+ err = sysfs_create_group(&client->dev.kobj, &max664x_group);
+ if (err)
+ goto exit_free;
+
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ goto exit_remove;
+ }
+
+ return 0;
+
+exit_remove:
+ sysfs_remove_group(&client->dev.kobj, &max664x_group);
+exit_free:
+ kfree(data);
+ i2c_set_clientdata(client, NULL);
+ return err;
+}
+
+static int max664x_remove(struct i2c_client *client)
+{
+ struct max664x_data *data = i2c_get_clientdata(client);
+
+ hwmon_device_unregister(data->hwmon_dev);
+ sysfs_remove_group(&client->dev.kobj, &max664x_group);
+
+ kfree(data);
+ i2c_set_clientdata(client, NULL);
+ return 0;
+}
+
+static const struct i2c_device_id max664x_id[] = {
+ { "max6646" },
+ { "max6647" },
+ { "max6649" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max664x_id);
+
+struct i2c_driver max664x_driver = {
+ .driver = {
+ .name = "max664x",
+ },
+ .probe = max664x_probe,
+ .remove = max664x_remove,
+ .id_table = max664x_id,
+};
+
+static int __init max664x_init(void)
+{
+ return i2c_add_driver(&max664x_driver);
+}
+
+static void __exit max664x_exit(void)
+{
+ i2c_del_driver(&max664x_driver);
+}
+
+MODULE_AUTHOR("Solarflare Communications");
+MODULE_DESCRIPTION("MAX6646/6647/6649 driver");
+MODULE_LICENSE("GPL v2");
+
+module_init(max664x_init);
+module_exit(max664x_exit);
diff --git a/include/linux/max664x.h b/include/linux/max664x.h
new file mode 100644
index 0000000..0b05903
--- /dev/null
+++ b/include/linux/max664x.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+ * Interface to max664x driver
+ * Copyright 2008 Solarflare Communications Inc.
+ *
+ * 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, incorporated herein by reference.
+ */
+
+#ifndef _MAX664X_H_
+#define _MAX664X_H_
+
+#ifdef __KERNEL__
+
+#include <linux/types.h>
+#include <linux/i2c.h>
+
+/* Status register */
+#define MAX664X_STATUS_IOT (1 << 0)
+#define MAX664X_STATUS_EOT (1 << 1)
+#define MAX664X_STATUS_FAULT (1 << 2)
+#define MAX664X_STATUS_RLOW (1 << 3)
+#define MAX664X_STATUS_RHIGH (1 << 4)
+#define MAX664X_STATUS_LLOW (1 << 5)
+#define MAX664X_STATUS_LHIGH (1 << 6)
+#define MAX664X_STATUS_BUSY (1 << 7)
+
+#define MAX664X_TEMP_INT 0
+#define MAX664X_TEMP_EXT 1
+
+/**
+ * struct max664x_settings - settings for MAX6646/6647/6649 hardware monitor
+ * @rate: Conversion rate setting
+ * @temp_overt: High temperature to set OVERT
+ * @temp_high_alert: High temperature to set ALERT
+ * @temp_low_alert: Low temperature to set ALERT
+ * @temp_overt_hyst: Hysteresis for resetting OVERT
+ *
+ * All values are raw register values.
+ */
+struct max664x_settings {
+ u8 rate;
+ u8 temp_overt[2];
+ u8 temp_high_alert[2];
+ u8 temp_low_alert[2];
+ u8 temp_overt_hyst;
+};
+
+/**
+ * struct max664x_values - values from MAX6646/6647/6649 hardware monitor
+ * @temp: Current temperature
+ *
+ * All values are raw register values.
+ */
+struct max664x_values {
+ u8 temp[2];
+};
+
+/**
+ * struct max664x_platform_data - platform data for MAX6646/6647/6649 hardware monitor
+ * @set_rate: Flag for whether to set rate register
+ * @set_limits: Flag for whether to set limit registers
+ * @set: Settings to be applied depending on the above flags
+ *
+ * This platform data is optional. If not provided, the driver will
+ * assume that the MAX6646/6647/6649 was properly configured by firmware
+ * or the power-on-reset defaults.
+ */
+struct max664x_platform_data {
+ unsigned set_rate : 1;
+ unsigned set_limits : 1;
+ struct max664x_settings set;
+};
+
+/**
+ * max664x_update_values() - update and return current values
+ * @client: I2C client to which the max664x driver is bound
+ */
+struct max664x_values *max664x_update_values(struct i2c_client *client);
+
+/**
+ * max664x_read_status() - read status register
+ * @client: I2C client to which the max664x driver is bound
+ *
+ * Read the status register, which automatically clears any stuck alarm
+ * bits.
+ */
+int max664x_read_status(struct i2c_client *client);
+
+#endif
+
+#endif
--
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
More information about the lm-sensors
mailing list