[PATCH v2] hwmon: added kernel module for FTS BMC chip "Teutates"

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Thilo Cestonaro <thilo@xxxxxxxxxx>

This driver implements support for the FTS BMC Chip "Teutates".

Signed-off-by: Thilo Cestonaro <thilo@xxxxxxxxxx>
---
 Documentation/hwmon/ftsteutates |  24 ++
 drivers/hwmon/Kconfig           |  11 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/ftsteutates.c     | 731 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 767 insertions(+)
 create mode 100644 Documentation/hwmon/ftsteutates
 create mode 100644 drivers/hwmon/ftsteutates.c

diff --git a/Documentation/hwmon/ftsteutates b/Documentation/hwmon/ftsteutates
new file mode 100644
index 0000000..318c9eb
--- /dev/null
+++ b/Documentation/hwmon/ftsteutates
@@ -0,0 +1,24 @@
+Kernel driver ftsteutates
+=====================
+
+Supported chips:
+  * FTS Teutates
+    Prefix: 'ftsteutates'
+    Addresses scanned: I2C 0x73 (7-Bit)
+
+Author: Thilo Cestonaro <thilo.cestonaro@xxxxxxxxxxxxxx>
+
+
+Description
+-----------
+The BMC Teutates is the Eleventh generation of Superior System
+monitoring and thermal management solution. It is builds on the basic
+functionality of the BMC Theseus and contains several new features and
+enhancements. It can monitor up to 4 voltages, 16 temperatures and
+8 fans. It also contains an integrated watchdog which is currently
+implemented in this driver.
+
+Specification of the chip can be found here:
+ftp://ftp.ts.fujitsu.com/pub/Mainboard-OEM-Sales/Products/Mainboards/ \
+	Industrial&ExtendedLifetime/D344x-S/IndustrialTools_D344x-S/      \
+	Linux_SystemMonitoring&Watchdog&GPIO
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index ff94007..5c98e55 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -486,6 +486,17 @@ config SENSORS_FSCHMD
 	  This driver can also be built as a module.  If so, the module
 	  will be called fschmd.
 
+config SENSORS_FTSTEUTATES
+	tristate "Fujitsu Technology Solutions sensor chip Teutates"
+	depends on X86 && I2C
+	help
+	  If you say yes here you get support for the Fujitsu Technology
+      Solutions (FTS) sensor chip "Teutates" including support for
+      the integrated watchdog.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called ftsteutates.
+
 config SENSORS_GL518SM
 	tristate "Genesys Logic GL518SM"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 2ef5b7c..dcad5f7 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
 obj-$(CONFIG_SENSORS_F75375S)	+= f75375s.o
 obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o
 obj-$(CONFIG_SENSORS_FSCHMD)	+= fschmd.o
+obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o
 obj-$(CONFIG_SENSORS_G760A)	+= g760a.o
 obj-$(CONFIG_SENSORS_G762)	+= g762.o
 obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
diff --git a/drivers/hwmon/ftsteutates.c b/drivers/hwmon/ftsteutates.c
new file mode 100644
index 0000000..0381e84
--- /dev/null
+++ b/drivers/hwmon/ftsteutates.c
@@ -0,0 +1,731 @@
+/*
+ * ftsteutates.c, Support for the FTS Systemmonitoring Chip "Teutates"
+ *
+ * Copyright (C) 2016 Fujitsu Technology Solutions GmbH,
+ *		  Thilo Cestonaro <thilo.cestonaro@xxxxxxxxxxxxxx>
+ *
+ * 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.
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+
+#define FTSTEUTATES_DEVICE_ID_REG			0x0000
+#define FTSTEUTATES_DEVICE_REVISION_REG		0x0001
+#define FTSTEUTATES_DEVICE_STATUS_REG		0x0004
+#define FTSTEUTATES_SATELLITE_STATUS_REG	0x0005
+#define FTSTEUTATES_EVENT_STATUS_REG		0x0006
+#define FTSTEUTATES_GLOBAL_CONTROL_REG		0x0007
+
+#define FTSTEUTATES_SENSOR_EVENT_REG		0x0010
+
+#define FTSTEUTATES_FAN_EVENT_REG			0x0014
+#define FTSTEUTATES_FAN_PRESENT_REG			0x0015
+
+#define FTSTEUTATES_POWER_ON_TIME_COUNTER_A 0x007A
+#define FTSTEUTATES_POWER_ON_TIME_COUNTER_B 0x007B
+#define FTSTEUTATES_POWER_ON_TIME_COUNTER_C 0x007C
+
+#define FTSTEUTATES_PAGE_SELECT_REG			0x007F
+
+#define FTSTEUTATES_WATCHDOG_TIME_PRESET	0x000B
+
+/* currently undocumented register: Bit1 = 1 => 1 second watchdog resolution */
+#define FTSTEUTATES_WATCHDOG_CONTROL		0x5081
+#define FTSTEUTATES_WATCHDOG_RESOLUTION		1
+
+#define FTSTEUTATES_NO_FAN_SENSORS			0x08
+#define FTSTEUTATES_NO_TEMP_SENSORS			0x10
+#define FTSTEUTATES_NO_VOLT_SENSORS			0x04
+
+#define temp_sysfs_attr_group(offset) \
+static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, show_temp_value, \
+			  NULL, offset - 1); \
+static SENSOR_DEVICE_ATTR(temp##offset##_fault, S_IRUGO, show_temp_fault, \
+			  NULL, offset - 1); \
+static SENSOR_DEVICE_ATTR(temp##offset##_alarm, S_IRUGO | S_IWUSR, \
+			 show_temp_alarm, clear_temp_alarm, offset - 1); \
+static struct attribute *ftsteutates_temp##offset##_attrs[] = { \
+	&sensor_dev_attr_temp##offset##_input.dev_attr.attr, \
+	&sensor_dev_attr_temp##offset##_fault.dev_attr.attr, \
+	&sensor_dev_attr_temp##offset##_alarm.dev_attr.attr, \
+	NULL \
+}; \
+static struct attribute_group ftsteutates_temp##offset##_attrs_group = { \
+	.attrs = ftsteutates_temp##offset##_attrs \
+}
+
+#define fan_sysfs_attr_group(offset) \
+static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, show_fan_value, NULL, \
+			  offset - 1); \
+static SENSOR_DEVICE_ATTR(fan##offset##_source, S_IRUGO, show_fan_source, \
+			  NULL, offset - 1); \
+static SENSOR_DEVICE_ATTR(fan##offset##_alarm, S_IRUGO | S_IWUSR, \
+			 show_fan_alarm, clear_fan_alarm, offset - 1); \
+static struct attribute *ftsteutates_fan##offset##_attrs[] = { \
+	&sensor_dev_attr_fan##offset##_input.dev_attr.attr, \
+	&sensor_dev_attr_fan##offset##_source.dev_attr.attr, \
+	&sensor_dev_attr_fan##offset##_alarm.dev_attr.attr, \
+	NULL \
+}; \
+static struct attribute_group ftsteutates_fan##offset##_attrs_group = { \
+	.is_visible = ftsteutates_fan_isvisible, \
+	.attrs = ftsteutates_fan##offset##_attrs \
+}
+
+/* possible addresses */
+static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
+
+static struct i2c_device_id ftsteutates_id[] = {
+	{ "ftsteutates", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ftsteutates_id);
+
+struct ftsteutates_data {
+	struct i2c_client *client;
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	bool valid; /* zero until following fields are valid */
+	unsigned long last_updated; /* in jiffies */
+
+	/* voltage */
+	u8 volt[FTSTEUTATES_NO_VOLT_SENSORS];
+
+	/* temprature */
+	u8 temp_input[FTSTEUTATES_NO_TEMP_SENSORS]; /* value */
+	bool temp_alarm[FTSTEUTATES_NO_TEMP_SENSORS]; /* alarm */
+
+	/* fan */
+	bool fan_present[FTSTEUTATES_NO_FAN_SENSORS]; /* presence */
+	u8 fan_input[FTSTEUTATES_NO_FAN_SENSORS]; /* revolutions per second */
+	u8 fan_source[FTSTEUTATES_NO_FAN_SENSORS]; /* sensor source */
+	bool fan_alarm[FTSTEUTATES_NO_FAN_SENSORS]; /* alarm */
+};
+DEFINE_MUTEX(access_lock);
+static bool watchdog_initialized = 0;
+
+/* input fan speed */
+static const u16 FTSTEUTATES_REG_FAN_input[FTSTEUTATES_NO_FAN_SENSORS] = {
+	  0x0020, 0x0021, 0x0022, 0x0023,
+	  0x0024, 0x0025, 0x0026, 0x0027 /* teutates */
+};
+/* fan sensor source */
+static const u16 FTSTEUTATES_REG_FAN_source[FTSTEUTATES_NO_FAN_SENSORS] = {
+	  0x0030, 0x0031, 0x0032, 0x0033,
+	  0x0034, 0x0035, 0x0036, 0x0037 /* teutates */
+};
+/* fan control */
+static const u16 FTSTEUTATES_REG_FAN_control[FTSTEUTATES_NO_FAN_SENSORS] = {
+	  0x4881, 0x4981, 0x4A81, 0x4B81,
+	  0x4C81, 0x4D81, 0x4E81, 0x4F81 /* teutates */
+};
+
+/* input temprature */
+static const u16 FTSTEUTATES_REG_TEMP_input[FTSTEUTATES_NO_TEMP_SENSORS] = {
+	  0x0040, 0x0041, 0x0042, 0x0043, 0x0044,
+	  0x0045, 0x0046, 0x0047, 0x0048, 0x0049,
+	  0x004A, 0x004B, 0x004C, 0x004D, 0x004E,
+	  0x004F /* teutates */
+};
+/* control temprature */
+static const u16 FTSTEUTATES_REG_TEMP_control[FTSTEUTATES_NO_TEMP_SENSORS] = {
+	  0x0681, 0x0781, 0x0881, 0x0981, 0x0A81,
+	  0x0B81, 0x0C81, 0x0D81, 0x0E81, 0x0F81,
+	  0x1081, 0x1181, 0x1281, 0x1381, 0x1481,
+	  0x1581 /* teutates */
+};
+
+/* voltage in */
+static const u16 FTSTEUTATES_REG_VOLT[FTSTEUTATES_NO_VOLT_SENSORS] = {
+	  0x001A, 0x0018, 0x0019, 0x001B /* teutates */
+};
+
+/*****************************************************************************/
+/* I2C Helper functions							     */
+/*****************************************************************************/
+int ftsteutates_read_byte(struct i2c_client *client, unsigned short reg)
+{
+	int ret = -1;
+	unsigned char page = reg>>8;
+
+	mutex_lock(&access_lock);
+	dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page);
+	ret = i2c_smbus_write_byte_data(client, FTSTEUTATES_PAGE_SELECT_REG,
+				page);
+
+	if (ret != 0)
+		goto error;
+
+	reg &= 0xFF;
+	ret = i2c_smbus_read_byte_data(client, reg);
+	dev_dbg(&client->dev, "read - reg: 0x%.02x: val: 0x%.02x\n", reg, ret);
+
+error:
+	mutex_unlock(&access_lock);
+	return ret;
+}
+
+int ftsteutates_write_byte(struct i2c_client *client, unsigned short reg,
+						   unsigned char value)
+{
+	int ret;
+	unsigned char page = reg>>8;
+
+	mutex_lock(&access_lock);
+	dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page);
+	ret = i2c_smbus_write_byte_data(client, FTSTEUTATES_PAGE_SELECT_REG,
+				page);
+
+	if (ret != 0)
+		goto error;
+
+	reg &= 0xFF;
+	dev_dbg(&client->dev,
+		"write - reg: 0x%.02x: val: 0x%.02x\n", reg, value);
+	ret = i2c_smbus_write_byte_data(client, reg, value);
+
+error:
+	mutex_unlock(&access_lock);
+	return ret;
+}
+
+/*****************************************************************************/
+/* Data Updater Helper function						     */
+/*****************************************************************************/
+static struct ftsteutates_data *ftsteutates_update_device(struct device *dev)
+{
+	struct ftsteutates_data *data = dev_get_drvdata(dev);
+	int i;
+	int present, alarm, status;
+
+	mutex_lock(&data->update_lock);
+	if (!time_after(jiffies, data->last_updated + 2 * HZ) && data->valid)
+		goto exit;
+
+	status = ftsteutates_read_byte(data->client,
+				       FTSTEUTATES_DEVICE_STATUS_REG);
+	if(status < 0) {
+		dev_err(dev, "couldn't read device status register\n");
+		goto exit;
+	}
+	data->valid = !!(status & 0x02);
+	if (!data->valid)
+		goto exit;
+
+	present = ftsteutates_read_byte(data->client,
+					FTSTEUTATES_FAN_PRESENT_REG);
+	if(present < 0) {
+		dev_err(dev, "couldn't read fan present register\n");
+		goto exit;
+	}
+
+	alarm = ftsteutates_read_byte(data->client, FTSTEUTATES_FAN_EVENT_REG);
+	if(present < 0) {
+		dev_err(dev, "couldn't read fan event register\n");
+		goto exit;
+	}
+
+	for (i = 0; i < FTSTEUTATES_NO_FAN_SENSORS; i++) {
+		data->fan_present[i] = !!(present & (1<<i));
+		if (data->fan_present[i]) {
+			data->fan_alarm[i] = !!(alarm & (1<<i));
+
+			data->fan_input[i] = ftsteutates_read_byte(data->client,
+						  FTSTEUTATES_REG_FAN_input[i]);
+			if(data->fan_input[i] < 0) {
+				dev_err(dev,
+					"couldn't read fan input register\n");
+				goto exit;
+			}
+
+			data->fan_source[i] = ftsteutates_read_byte(
+						 data->client,
+						 FTSTEUTATES_REG_FAN_source[i]);
+			if(data->fan_source[i] < 0) {
+				dev_err(dev,
+					"couldn't read fan source register\n");
+				goto exit;
+			}
+		} else {
+			data->fan_alarm[i] = 0;
+			data->fan_input[i] = 0;
+			data->fan_source[i] = 0;
+		}
+	}
+
+	alarm = ftsteutates_read_byte(data->client,
+				      FTSTEUTATES_SENSOR_EVENT_REG);
+	if(alarm < 0) {
+		dev_err(dev, "couldn't read sensor alarm register\n");
+		goto exit;
+	}
+
+	for (i = 0; i < FTSTEUTATES_NO_TEMP_SENSORS; i++) {
+		data->temp_input[i] = ftsteutates_read_byte(data->client,
+						FTSTEUTATES_REG_TEMP_input[i]);
+		if(data->temp_input[i] < 0) {
+			dev_err(dev,
+				"couldn't read temperature input register\n");
+			goto exit;
+		}
+		data->temp_alarm[i] = !!(alarm & (1<<i));
+	}
+
+	for (i = 0; i < FTSTEUTATES_NO_VOLT_SENSORS; i++) {
+		data->volt[i] = ftsteutates_read_byte(data->client,
+						      FTSTEUTATES_REG_VOLT[i]);
+		if(data->volt[i] < 0) {
+			dev_err(dev, "couldn't read voltage register\n");
+			goto exit;
+		}
+	}
+	data->last_updated = jiffies;
+
+exit:
+	mutex_unlock(&data->update_lock);
+	return data;
+}
+
+/*****************************************************************************/
+/* Watchdog functions							     */
+/*****************************************************************************/
+static int ftsteutates_wdt_set_timeout(struct watchdog_device *wdd,
+				       unsigned int timeout_seconds)
+{
+	wdd->timeout = DIV_ROUND_UP(wdd->timeout,
+				    FTSTEUTATES_WATCHDOG_RESOLUTION);
+	return 0;
+}
+
+static int ftsteutates_wdt_start(struct watchdog_device *wdd)
+{
+	struct ftsteutates_data *data;
+
+	data = watchdog_get_drvdata(wdd);
+	return ftsteutates_write_byte(data->client,
+			       FTSTEUTATES_WATCHDOG_TIME_PRESET, wdd->timeout);
+}
+
+static int ftsteutates_wdt_stop(struct watchdog_device *wdd)
+{
+	struct ftsteutates_data *data;
+
+	data = watchdog_get_drvdata(wdd);
+	return ftsteutates_write_byte(data->client,
+			FTSTEUTATES_WATCHDOG_TIME_PRESET, 0);
+}
+
+static const struct watchdog_info ftsteutates_wdt_info = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity = "FTS Teutates Hardware Watchdog",
+};
+
+static const struct watchdog_ops ftsteutates_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = ftsteutates_wdt_start,
+	.stop = ftsteutates_wdt_stop,
+	.set_timeout = ftsteutates_wdt_set_timeout,
+};
+
+static struct watchdog_device ftsteutates_wdt = {
+	.info = &ftsteutates_wdt_info,
+	.ops = &ftsteutates_wdt_ops,
+};
+
+/*****************************************************************************/
+/* SysFS handler functions						     */
+/*****************************************************************************/
+static const int multi[4] = { 11, 11, 39, 10 };
+static ssize_t show_in_value(struct device *dev,
+			     struct device_attribute *devattr, char *buf)
+{
+	/* got from the systemboard specification */
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+	unsigned int val = (multi[index] * data->volt[index] * 330) / 256;
+
+	return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t show_temp_value(struct device *dev,
+			       struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+	int value = data->temp_input[index];
+
+	/* 00h Temperature = Sensor Error */
+	return sprintf(buf, "%d\n", value > 0 ? (value - 64) * 1000 : 0);
+}
+
+static ssize_t show_temp_fault(struct device *dev,
+			       struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+
+	/* 00h Temperature = Sensor Error */
+	return sprintf(buf, "%d\n", data->temp_input[index] == 0);
+}
+
+static ssize_t show_temp_alarm(struct device *dev,
+			       struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+
+	return sprintf(buf, "%u\n", data->temp_alarm[index]);
+}
+
+static ssize_t
+clear_temp_alarm(struct device *dev, struct device_attribute *devattr,
+				 const char *buf, size_t count)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+	unsigned long val;
+	unsigned char reg;
+
+	if (kstrtoul(buf, 10, &val) || val != 0)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	reg = ftsteutates_read_byte(data->client,
+				    FTSTEUTATES_REG_TEMP_control[index]);
+	if(reg < 0) {
+		dev_err(dev, "couldn't read temp control register\n");
+		count = -EIO;
+		goto error;
+	}
+
+	val = ftsteutates_write_byte(data->client,
+				     FTSTEUTATES_REG_TEMP_control[index],
+				     reg | 0x1);
+	if(val < 0) {
+		dev_err(dev, "couldn't read temp control register\n");
+		count = -EIO;
+		goto error;
+	}
+	data->valid = 0;
+
+error:
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static umode_t ftsteutates_fan_isvisible(struct kobject *kobj,
+					 struct attribute *attr, int index)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+	struct device_attribute *dev_attr = container_of(attr,
+						struct device_attribute, attr);
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(dev_attr);
+	umode_t ret = attr->mode;
+
+	if(!data->valid || !data->fan_present[sattr->index])
+		ret = 0;
+
+	return ret;
+}
+
+static ssize_t show_fan_value(struct device *dev,
+			      struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+
+	return sprintf(buf, "%u\n", data->fan_input[index] * 60);
+}
+
+static ssize_t show_fan_source(struct device *dev,
+			       struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+
+	if (data->fan_present[index]) {
+		if (data->fan_source[index] == 0xFF)
+			return sprintf(buf, "none\n");
+		else
+			return sprintf(buf, "%d\n", data->fan_source[index]);
+	} else
+		return sprintf(buf, "-\n");
+}
+
+static ssize_t show_fan_alarm(struct device *dev,
+			      struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+
+	return sprintf(buf, "%d\n", data->fan_alarm[index]);
+}
+
+static ssize_t
+clear_fan_alarm(struct device *dev, struct device_attribute *devattr,
+				const char *buf, size_t count)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct ftsteutates_data *data = ftsteutates_update_device(dev);
+	unsigned long val;
+	unsigned char reg;
+
+	if (kstrtoul(buf, 10, &val) || val != 0)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	reg = ftsteutates_read_byte(data->client,
+		FTSTEUTATES_REG_FAN_control[index]);
+	if(reg < 0) {
+		dev_err(dev, "couldn't read fan control register\n");
+		count = -EIO;
+		goto error;
+	}
+
+	val = ftsteutates_write_byte(data->client,
+		FTSTEUTATES_REG_FAN_control[index], reg | 0x1);
+	if(val < 0) {
+		dev_err(dev, "couldn't write fan control register\n");
+		count = -EIO;
+		goto error;
+	}
+	data->valid = 0;
+
+error:
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/*****************************************************************************/
+/* SysFS structs							     */
+/*****************************************************************************/
+temp_sysfs_attr_group(1);
+temp_sysfs_attr_group(2);
+temp_sysfs_attr_group(3);
+temp_sysfs_attr_group(4);
+temp_sysfs_attr_group(5);
+temp_sysfs_attr_group(6);
+temp_sysfs_attr_group(7);
+temp_sysfs_attr_group(8);
+temp_sysfs_attr_group(9);
+temp_sysfs_attr_group(10);
+temp_sysfs_attr_group(11);
+temp_sysfs_attr_group(12);
+temp_sysfs_attr_group(13);
+temp_sysfs_attr_group(14);
+temp_sysfs_attr_group(15);
+temp_sysfs_attr_group(16);
+
+fan_sysfs_attr_group(1);
+fan_sysfs_attr_group(2);
+fan_sysfs_attr_group(3);
+fan_sysfs_attr_group(4);
+fan_sysfs_attr_group(5);
+fan_sysfs_attr_group(6);
+fan_sysfs_attr_group(7);
+fan_sysfs_attr_group(8);
+
+static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in_value, NULL, 0);
+static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in_value, NULL, 1);
+static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in_value, NULL, 2);
+static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in_value, NULL, 3);
+static struct attribute *ftsteutates_voltage_attrs[] = {
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in3_input.dev_attr.attr,
+	&sensor_dev_attr_in4_input.dev_attr.attr,
+	NULL
+};
+static struct attribute_group ftsteutates_voltage_attrs_group = {
+	.attrs = ftsteutates_voltage_attrs
+};
+
+static const struct attribute_group *ftsteutates_attr_groups[] = {
+	&ftsteutates_voltage_attrs_group,
+	&ftsteutates_temp1_attrs_group,
+	&ftsteutates_temp2_attrs_group,
+	&ftsteutates_temp3_attrs_group,
+	&ftsteutates_temp4_attrs_group,
+	&ftsteutates_temp5_attrs_group,
+	&ftsteutates_temp6_attrs_group,
+	&ftsteutates_temp7_attrs_group,
+	&ftsteutates_temp8_attrs_group,
+	&ftsteutates_temp9_attrs_group,
+	&ftsteutates_temp10_attrs_group,
+	&ftsteutates_temp11_attrs_group,
+	&ftsteutates_temp12_attrs_group,
+	&ftsteutates_temp13_attrs_group,
+	&ftsteutates_temp14_attrs_group,
+	&ftsteutates_temp15_attrs_group,
+	&ftsteutates_temp16_attrs_group,
+	&ftsteutates_fan1_attrs_group,
+	&ftsteutates_fan2_attrs_group,
+	&ftsteutates_fan3_attrs_group,
+	&ftsteutates_fan4_attrs_group,
+	&ftsteutates_fan5_attrs_group,
+	&ftsteutates_fan6_attrs_group,
+	&ftsteutates_fan7_attrs_group,
+	&ftsteutates_fan8_attrs_group,
+	NULL
+};
+
+/*****************************************************************************/
+/* Module initialization / remove functions				     */
+/*****************************************************************************/
+static int ftsteutates_detect(struct i2c_client *client,
+			      struct i2c_board_info *info)
+{
+	int val = ftsteutates_read_byte(client, FTSTEUTATES_DEVICE_ID_REG);
+
+	/* Baseboard Management Controller */
+	if (val > 0 && (val & 0xF0) == 0x10) {
+		switch (val & 0x0F) {
+		case 0x01:
+			strlcpy(info->type, ftsteutates_id[0].name,
+			I2C_NAME_SIZE);
+			info->flags = 0;
+			return 0;
+		}
+	}
+	return -ENODEV;
+}
+
+static int ftsteutates_remove(struct i2c_client *client)
+{
+	if(watchdog_initialized) {
+		watchdog_unregister_device(&ftsteutates_wdt);
+		ftsteutates_wdt_stop(&ftsteutates_wdt);
+	}
+	return 0;
+}
+
+static int ftsteutates_watchdog_init(struct ftsteutates_data *data)
+{
+	int ret;
+
+	ret = ftsteutates_read_byte(data->client,
+				    FTSTEUTATES_WATCHDOG_CONTROL);
+	if(ret < 0) {
+		dev_err(&data->client->dev,
+			"couldn't read watchdog control register\n");
+		return ret;
+	}
+
+	ret = ftsteutates_write_byte(data->client,
+				     FTSTEUTATES_WATCHDOG_CONTROL,
+				     ret | 0x2);
+	if(ret < 0) {
+		dev_err(&data->client->dev,
+			"couldn't set watchdog resolution\n");
+		return ret;
+	}
+
+	ret = ftsteutates_read_byte(data->client,
+				    FTSTEUTATES_WATCHDOG_TIME_PRESET);
+	if(ret < 0) {
+		dev_err(&data->client->dev,
+			"couldn't read watchdog time preset register\n");
+		return ret;
+	}
+
+	/* Register our watchdog part */
+	ftsteutates_wdt.parent = &data->client->dev;
+	ftsteutates_wdt.timeout = ret * FTSTEUTATES_WATCHDOG_RESOLUTION;
+	ftsteutates_wdt.min_timeout = FTSTEUTATES_WATCHDOG_RESOLUTION * 1;
+	ftsteutates_wdt.max_timeout = FTSTEUTATES_WATCHDOG_RESOLUTION * 0xFF;
+	watchdog_set_drvdata(&ftsteutates_wdt, data);
+	ret = watchdog_register_device(&ftsteutates_wdt);
+	return ret;
+}
+
+static int ftsteutates_probe(struct i2c_client *client,
+			     const struct i2c_device_id *id)
+{
+	u8 revision;
+	struct ftsteutates_data *data;
+	int err;
+
+	data = devm_kzalloc(&client->dev, sizeof(struct ftsteutates_data),
+			    GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	mutex_init(&data->update_lock);
+	data->client = client;
+
+	revision = ftsteutates_read_byte(client,
+					 FTSTEUTATES_DEVICE_REVISION_REG);
+	if(revision < 0) {
+		dev_err(&client->dev,
+			"couldn't read device revision register\n");
+		return revision;
+	}
+
+	data->hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev,
+					"ftsteutates", data,
+					ftsteutates_attr_groups);
+	if (IS_ERR(data->hwmon_dev)) {
+		err = PTR_ERR(data->hwmon_dev);
+		dev_err(&client->dev, "could not register hwmon device\n");
+		goto exit_detach;
+	}
+
+	err = ftsteutates_watchdog_init(data);
+	if (err) {
+		dev_err(&client->dev,
+			"Registering watchdog device failed: %d\n", err);
+		goto exit_detach;
+	}
+	watchdog_initialized = 1;
+
+	dev_info(&client->dev, "Detected FTS Teutates chip, revision: %d.%d\n",
+		 (revision & 0xF0)>>4, revision & 0x0F);
+	return 0;
+
+exit_detach:
+	ftsteutates_remove(client); /* will also free data for us */
+	return err;
+}
+
+/*****************************************************************************/
+/* Module Details							     */
+/*****************************************************************************/
+static struct i2c_driver ftsteutates_driver = {
+	.class	  = I2C_CLASS_HWMON,
+	.driver = {
+		.name	 = "ftsteutates",
+	},
+	.id_table	= ftsteutates_id,
+	.probe	  = ftsteutates_probe,
+	.remove	   = ftsteutates_remove,
+	.detect	   = ftsteutates_detect,
+	.address_list = normal_i2c,
+};
+
+module_i2c_driver(ftsteutates_driver);
+
+MODULE_AUTHOR("Thilo Cestonaro <thilo.cestonaro@xxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("FTS Teutates driver");
+MODULE_LICENSE("GPL");
-- 
2.8.1

--
To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [LM Sensors]     [Linux Sound]     [ALSA Users]     [ALSA Devel]     [Linux Audio Users]     [Linux Media]     [Kernel]     [Gimp]     [Yosemite News]     [Linux Media]

  Powered by Linux