lkml.org 
[lkml]   [2013]   [Nov]   [8]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCHv0] hwmon: Add support for GMT G751 Temp. Sensor and Thermal Watchdog
Date

This patch adds support for GMT G751 Temperature Sensor and Thermal
Watchdog I2C chip. It has been tested via DT on a Netgear ReadyNAS
2120 (Marvell Armada XP based ARM device).

Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
Documentation/devicetree/bindings/hwmon/g751.txt | 24 +
.../devicetree/bindings/vendor-prefixes.txt | 1 +
Documentation/hwmon/g751 | 44 ++
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/g751.c | 481 +++++++++++++++++++++
include/linux/platform_data/g751.h | 35 ++
7 files changed, 597 insertions(+)
create mode 100644 Documentation/devicetree/bindings/hwmon/g751.txt
create mode 100644 Documentation/hwmon/g751
create mode 100644 drivers/hwmon/g751.c
create mode 100644 include/linux/platform_data/g751.h

diff --git a/Documentation/devicetree/bindings/hwmon/g751.txt b/Documentation/devicetree/bindings/hwmon/g751.txt
new file mode 100644
index 0000000..ebec788
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/g751.txt
@@ -0,0 +1,24 @@
+GMT G751 Digital Temperature Sensor and Thermal Watchdog
+
+Required node properties:
+
+ - "compatible": must be either "gmt,g751"
+ - "reg": I2C bus address of the device
+
+Optional properties:
+
+ - "polarity": Over temperature Shutdown (OS) output polarity. Accepted values
+ are 0 and 1. 0 (the default) is used to make the output active
+ low. 1 makes the output active high.
+
+Additional information on operational parameters for the device is available
+in Documentation/hwmon/g751. A detailed datasheet for the device is available
+at http://natisbad.org/NAS4/refs/GMT_G751.pdf.
+
+Example g751 node:
+
+ g751: g751@4c {
+ compatible = "gmt,g751";
+ reg = <0x4c>;
+ polarity = <1>; /* OS output Active High */
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 2956800..634c35f 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -28,6 +28,7 @@ est ESTeem Wireless Modems
fsl Freescale Semiconductor
GEFanuc GE Fanuc Intelligent Platforms Embedded Systems, Inc.
gef GE Fanuc Intelligent Platforms Embedded Systems, Inc.
+gmt Global Mixed-mode Technology Inc.
hisilicon Hisilicon Limited.
hp Hewlett Packard
ibm International Business Machines (IBM)
diff --git a/Documentation/hwmon/g751 b/Documentation/hwmon/g751
new file mode 100644
index 0000000..3508d53
--- /dev/null
+++ b/Documentation/hwmon/g751
@@ -0,0 +1,44 @@
+Kernel driver g751
+==================
+
+The GMT G751 is an I2C digital temperature sensor and thermal watchdog. For
+additional information, a detailed datasheet of the chip is available at
+http://natisbad.org/NAS4/refs/GMT_G751.pdf.
+
+sysfs bindings (found in a subdirectory of/sys/bus/i2c/drivers/g751/) are
+described below. They are available to the user to control the operation
+of the device. The main information provided by the device (temperature,
+via temp_input) is usually used by a userland daemon like fancontrol.
+
+Note that polarity of Over temperature Shutdown (OS) output is considered
+a hardware characteristics of the system and can be modified via devicetree
+bindings documented in Documentation/devicetree/bindings/hwmon/g751.txt or
+using a specific platform_data structure in board initialization file (see
+include/linux/platform_data/g751.h).
+
+temp_input: current temperature input value in millidegree Celsius. This
+ parameter is RO.
+
+thyst: defined Thyst value in millidegree Celsius. See 'mode' below for
+ details. Default value depends on G751 flavour (45000 for G751-1,
+ 75000 for G751-2). This parameter is RW.
+
+tos: defined Tos value in millidegree Celsius. See 'mode' below for details.
+ Default value depends on G751 flavour (50000 for G751-1, 80000 for
+ G751-2). This parameter is RW.
+
+fault_queue: number of faults necessary to detect before setting OS output.
+ This can be used to avoid false tripping due to noise. Valid values
+ are 1 (default), 2, 4 and 6. This parameter is RW.
+
+mode: used to toggle between comparatore mode (0, default mode) and interrupt
+ mode (1). In comparator mode, the OS output behaves like a thermostat
+ and becomes active when temperature exceeds Tos limit. It then leaves
+ the active state when the temperature drops again below Thyst. In
+ interrupt mode, exceeding Tos also makes OS output active in which case
+ it will remain active until reading any register. It can then be activated
+ again only after temperature goes below Thyst. This parameter is RW.
+
+shutdown: when set to 1 (0 being the default), the G751 goes to low power
+ shutdown mode. Placing G751 in shutdown mode also has the effect of
+ resetting the OS output. This parameter is RW.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b3ab9d4..524d1d7 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -451,6 +451,17 @@ config SENSORS_FSCHMD
This driver can also be built as a module. If so, the module
will be called fschmd.

+config SENSORS_G751
+ tristate "GMT G751"
+ depends on I2C
+ help
+ If you say yes here you get support for Global Mixed-mode
+ Technology Inc G751 Digital Temperature Sensor and Thermal
+ Watchdog.
+
+ This driver can also be built as a module. If so, the module
+ will be called g751.
+
config SENSORS_G760A
tristate "GMT G760A"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index ec7cde0..5c8bfc6 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -59,6 +59,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_G751) += g751.o
obj-$(CONFIG_SENSORS_G760A) += g760a.o
obj-$(CONFIG_SENSORS_G762) += g762.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
diff --git a/drivers/hwmon/g751.c b/drivers/hwmon/g751.c
new file mode 100644
index 0000000..9c85c41
--- /dev/null
+++ b/drivers/hwmon/g751.c
@@ -0,0 +1,481 @@
+/*
+ * g751 - Driver for the Global Mixed-mode Technology Inc. G751 digital
+ * temperature sensor and thermal watchdog chip.
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_data/g751.h>
+
+#define DRVNAME "g751"
+
+static const struct i2c_device_id g751_id[] = {
+ { "g751", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, g751_id);
+
+/* Four data registers can be addressed via a pointer register */
+enum g751_data_reg {
+ G751_REG_TEMP = 0x00, /* 16-bit reg */
+ G751_REG_CONF = 0x01, /* 8-bit reg */
+ G751_REG_THYST = 0x02, /* 16-bit reg */
+ G751_REG_TOS = 0x03, /* 16-bit reg */
+};
+
+/* Provide size of given register */
+#define G751_DATA_REG_LEN(reg) (((reg) == G751_REG_CONF) ? 1 : 2)
+
+/* Configuration register bits: shifts and masks */
+#define G751_CONF_FQUEUE_SHIFT 3
+#define G751_CONF_FQUEUE_MASK 0x03
+#define G751_CONF_POLARITY_SHIFT 2
+#define G751_CONF_POLARITY_MASK 0x02
+#define G751_CONF_MODE_SHIFT 1
+#define G751_CONF_MODE_MASK 0x01
+#define G751_CONF_SHUTDOWN_SHIFT 0
+#define G751_CONF_SHUTDOWN_MASK 0x01
+
+/* Temperature conversion helpers from/to register value */
+static inline void temp_from_reg(int32_t *temp, u8 *regval)
+{
+ int8_t *buf = (int8_t *)(regval);
+ *temp = (buf[0]*1000 + ((buf[1] & 0x80) ? 500 : 0));
+}
+
+static inline void temp_to_reg(u8 *regval, int32_t temp)
+{
+ regval[0] = (temp < 0 && temp/1000 == 0) ? 0xff : temp/1000;
+ regval[1] = ((temp/500) & 0x1) ? 0x80 : 0x00;
+}
+
+struct g751_data {
+ struct i2c_client *client;
+ struct device *hwmon_dev;
+ struct mutex lock;
+};
+
+/*
+ * Read content of 'reg' register and put result in 'val' buffer if
+ * everything went ok; 0 is returned in that case. A negative value
+ * is returned on error, in which case 'val' is not updated.
+ */
+static int g751_reg_read(struct device *dev, u8 reg, u8 *val)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ u8 buf[2] = { reg , 0 };
+ struct i2c_msg msgs[2] = {
+ {
+ .addr = client->addr,
+ .flags = client->flags,
+ .len = sizeof(u8),
+ .buf = buf
+ },
+ {
+ .addr = client->addr,
+ .flags = client->flags | I2C_M_RD,
+ .len = G751_DATA_REG_LEN(reg),
+ .buf = buf
+ }
+ };
+ int ret;
+
+ BUG_ON(reg > 3);
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: register read failed\n", __func__);
+ return ret;
+ }
+ memcpy(val, buf, G751_DATA_REG_LEN(reg));
+
+ return 0;
+}
+
+/*
+ * Write content of given 'val' buffer to 'reg' register. 0 is returned
+ * on success. A negative value is returned on error.
+ */
+static int g751_reg_write(struct device *dev, u8 reg, u8 *val)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ u8 buf[3] = { reg, 0, 0 };
+ struct i2c_msg msgs[1] = {
+ {
+ .addr = client->addr,
+ .flags = client->flags,
+ .len = G751_DATA_REG_LEN(reg) + 1,
+ .buf = buf
+ }
+ };
+ int ret;
+
+ BUG_ON(reg > 3 || reg == 0); /* temp reg (0) is read-only */
+
+ memcpy(&buf[1], val, G751_DATA_REG_LEN(reg));
+ ret = i2c_transfer(client->adapter, msgs, 1);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: register write failed\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Generic helper to extract some specific bits of conf register */
+static int conf_reg_bits_get(struct device *dev, u8 *val,
+ u8 bitshift, u8 bitmask)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct g751_data *data = i2c_get_clientdata(client);
+ u8 regval;
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = g751_reg_read(dev, G751_REG_CONF, &regval);
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ *val = (regval >> bitshift) & bitmask;
+
+ return 0;
+}
+
+/* Generic helper to set some specific bits of conf register */
+static int conf_reg_bits_set(struct device *dev, u8 val,
+ u8 bitshift, u8 bitmask)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct g751_data *data = i2c_get_clientdata(client);
+ u8 regval;
+ int ret;
+
+ mutex_lock(&data->lock);
+ ret = g751_reg_read(dev, G751_REG_CONF, &regval);
+ if (ret < 0)
+ goto out;
+
+ regval &= ~(bitmask << bitshift);
+ regval |= val << bitshift;
+
+ ret = g751_reg_write(dev, G751_REG_CONF, &regval);
+ out:
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+/*
+ * sysfs
+ */
+
+#define show_temp(value, reg) \
+static ssize_t show_##value(struct device *dev, struct device_attribute *da, \
+ char *buf) \
+{ \
+ u8 regval[2]; \
+ int32_t temp; \
+ int ret; \
+ \
+ ret = g751_reg_read(dev, reg, regval); \
+ if (ret < 0) \
+ return ret; \
+ temp_from_reg(&temp, regval); \
+ \
+ return sprintf(buf, "%d\n", temp); \
+}
+
+show_temp(temp_input, G751_REG_TEMP);
+show_temp(temp_hyst, G751_REG_THYST);
+show_temp(temp_os, G751_REG_TOS);
+
+#define set_temp(value, reg) \
+static ssize_t set_##value(struct device *dev, struct device_attribute *da, \
+ const char *buf, size_t count) \
+{ \
+ u8 regval[2]; \
+ int32_t temp; \
+ int ret; \
+ \
+ ret = kstrtos32(buf, 10, &temp); \
+ if (ret) \
+ return ret; \
+ temp_to_reg(regval, temp); \
+ \
+ ret = g751_reg_write(dev, reg, regval); \
+ if (ret < 0) \
+ return ret; \
+ \
+ return count; \
+}
+
+set_temp(temp_hyst, G751_REG_THYST);
+set_temp(temp_os, G751_REG_TOS);
+
+/*
+ * Read and write functions for fault_queue sysfs file. Get and set
+ * fault_queue length (either 1 (default), 2, 4 or 6).
+ */
+static ssize_t show_fqueue(struct device *dev, struct device_attribute *da,
+ char *buf)
+{
+ u8 regval;
+ int ret;
+
+ ret = conf_reg_bits_get(dev, &regval, G751_CONF_FQUEUE_SHIFT,
+ G751_CONF_FQUEUE_MASK);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", regval ? regval * 2 : 1);
+}
+
+static ssize_t set_fqueue(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ int ret;
+ u8 val;
+
+ if (kstrtou8(buf, 10, &val))
+ return -EINVAL;
+
+ switch (val) {
+ case 1:
+ val = 0;
+ break;
+ case 2:
+ case 4:
+ case 6:
+ val /= 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = conf_reg_bits_set(dev, val, G751_CONF_FQUEUE_SHIFT,
+ G751_CONF_FQUEUE_MASK);
+
+ return ret < 0 ? ret : count;
+}
+
+/*
+ * Read and write functions for mode sysfs file. Get and set mode (0 for
+ * comparator mode and 1 for interrupt mode).
+ */
+static ssize_t show_mode(struct device *dev, struct device_attribute *da,
+ char *buf)
+{
+ u8 regval;
+ int ret;
+
+ ret = conf_reg_bits_get(dev, &regval, G751_CONF_MODE_SHIFT,
+ G751_CONF_MODE_MASK);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", regval);
+}
+
+static ssize_t set_mode(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ int ret;
+ u8 val;
+
+ if (kstrtou8(buf, 10, &val) || val > 1)
+ return -EINVAL;
+
+ ret = conf_reg_bits_set(dev, val, G751_CONF_MODE_SHIFT,
+ G751_CONF_MODE_MASK);
+
+ return ret < 0 ? ret : count;
+}
+
+/*
+ * Read and write functions for shutdown sysfs file. Get and set low power
+ * shutdown mode (1 for low power shutdown mode).
+ */
+static ssize_t show_shutdown(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ u8 regval;
+ int ret;
+
+ ret = conf_reg_bits_get(dev, &regval, G751_CONF_MODE_SHIFT,
+ G751_CONF_MODE_MASK);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", regval);
+}
+
+static ssize_t set_shutdown(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ int ret;
+ u8 val;
+
+ if (kstrtou8(buf, 10, &val) || val > 1)
+ return -EINVAL;
+
+ ret = conf_reg_bits_set(dev, val, G751_CONF_SHUTDOWN_SHIFT,
+ G751_CONF_SHUTDOWN_MASK);
+
+ return ret < 0 ? ret : count;
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id g751_dt_match[] = {
+ { .compatible = "gmt,g751" },
+ { },
+};
+
+static void g751_of_import_polarity(struct i2c_client *client, int *pol)
+{
+ const __be32 *prop;
+ int len;
+
+ prop = of_get_property(client->dev.of_node, "polarity", &len);
+ if (!prop || len != sizeof(u32))
+ return;
+
+ *pol = !!be32_to_cpu(prop[0]);
+}
+#else
+static void g751_of_import_polarity(struct i2c_client *client, int *pol) {}
+#endif
+
+static DEVICE_ATTR(temp_input, S_IRUGO, show_temp_input, NULL);
+static DEVICE_ATTR(thyst, S_IRUGO|S_IWUSR, show_temp_hyst, set_temp_hyst);
+static DEVICE_ATTR(tos, S_IRUGO|S_IWUSR, show_temp_os, set_temp_os);
+static DEVICE_ATTR(fault_queue, S_IRUGO|S_IWUSR, show_fqueue, set_fqueue);
+static DEVICE_ATTR(mode, S_IRUGO|S_IWUSR, show_mode, set_mode);
+static DEVICE_ATTR(shutdown, S_IRUGO|S_IWUSR, show_shutdown, set_shutdown);
+
+/* Driver data */
+static struct attribute *g751_attributes[] = {
+ &dev_attr_temp_input.attr,
+ &dev_attr_thyst.attr,
+ &dev_attr_tos.attr,
+ &dev_attr_fault_queue.attr,
+ &dev_attr_mode.attr,
+ &dev_attr_shutdown.attr,
+ NULL
+};
+
+static const struct attribute_group g751_group = {
+ .name = "g751",
+ .attrs = g751_attributes,
+};
+
+static int g751_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct g751_platform_data *pdata;
+ struct g751_data *data;
+ int polarity = -1;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -ENODEV;
+
+ data = devm_kzalloc(&client->dev, sizeof(struct g751_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, data);
+ data->client = client;
+ mutex_init(&data->lock);
+
+ /* Change polarity if requested either via platform data or OF */
+ pdata = dev_get_platdata(&client->dev);
+ if (pdata)
+ polarity = !!pdata->polarity;
+ else
+ g751_of_import_polarity(client, &polarity);
+
+ if (polarity != -1) {
+ dev_dbg(&client->dev, "found polarity (%d)\n", polarity);
+
+ ret = conf_reg_bits_set(&client->dev, polarity,
+ G751_CONF_POLARITY_SHIFT,
+ G751_CONF_POLARITY_MASK);
+ if (ret)
+ dev_err(&client->dev, "unable to set polarity (%d)\n",
+ polarity);
+ }
+
+ /* Register sysfs hooks */
+ ret = sysfs_create_group(&client->dev.kobj, &g751_group);
+ if (ret)
+ goto out;
+
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ ret = PTR_ERR(data->hwmon_dev);
+ goto sysfs_clean;
+ }
+
+ return 0;
+
+ sysfs_clean:
+ sysfs_remove_group(&client->dev.kobj, &g751_group);
+
+ out:
+ return ret;
+}
+
+static int g751_remove(struct i2c_client *client)
+{
+ struct g751_data *data = i2c_get_clientdata(client);
+
+ hwmon_device_unregister(data->hwmon_dev);
+ sysfs_remove_group(&client->dev.kobj, &g751_group);
+
+ return 0;
+}
+
+static struct i2c_driver g751_driver = {
+ .driver = {
+ .name = DRVNAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(g751_dt_match),
+ },
+ .probe = g751_probe,
+ .remove = g751_remove,
+ .id_table = g751_id,
+};
+
+module_i2c_driver(g751_driver);
+
+MODULE_AUTHOR("Arnaud EBALARD <arno@natisbad.org>");
+MODULE_DESCRIPTION("GMT G751 driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/platform_data/g751.h b/include/linux/platform_data/g751.h
new file mode 100644
index 0000000..fec0415
--- /dev/null
+++ b/include/linux/platform_data/g751.h
@@ -0,0 +1,35 @@
+/*
+ * Platform data structure for g751 temperature sensor and thermal
+ * watchdog driver
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
+ *
+ * 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.
+ */
+#ifndef __LINUX_PLATFORM_DATA_G751_H__
+#define __LINUX_PLATFORM_DATA_G751_H__
+
+/*
+ * Following structure can be used to set g751 driver platform
+ * specific data during board init, i.e. over temperature (OS)
+ * output polarity: 0 for active low (the default), 1 for active
+ * high).
+ */
+
+struct g751_platform_data {
+ u8 polarity;
+};
+
+#endif /* __LINUX_PLATFORM_DATA_G751_H__ */
--
1.8.4.rc3


\
 
 \ /
  Last update: 2013-11-09 00:41    [W:0.047 / U:1.848 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site