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@xxxxxxxxxxxx> --- 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@xxxxxxxxxxxx> + * + * 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, ®val); + 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, ®val); + if (ret < 0) + goto out; + + regval &= ~(bitmask << bitshift); + regval |= val << bitshift; + + ret = g751_reg_write(dev, G751_REG_CONF, ®val); + 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, ®val, 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, ®val, 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, ®val, 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@xxxxxxxxxxxx>"); +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@xxxxxxxxxxxx> + * + * 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 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html