From c32f5eff2db51f0126b0a4e42bdefea2d9d2872e Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 29 Jan 2016 17:35:52 +0000 Subject: hwmon: (vexpress) rename vexpress hwmon implementation The vexpress hwmon implementation is currently just called vexpress. This is a problem because it clashes with another module with the same name in regulators. This patch renames the vexpress hwmon implementation to vexpress-hwmon so that there will be no clash in the module namespace. Cc: Liviu Dudau Cc: Lorenzo Pieralisi Cc: Jean Delvare Cc: Guenter Roeck Cc: lm-sensors@lm-sensors.org Reported-by: Rusty Russell Signed-off-by: Sudeep Holla Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 30c94df..cfc0971 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -149,7 +149,7 @@ obj-$(CONFIG_SENSORS_TMP103) += tmp103.o obj-$(CONFIG_SENSORS_TMP401) += tmp401.o obj-$(CONFIG_SENSORS_TMP421) += tmp421.o obj-$(CONFIG_SENSORS_TWL4030_MADC)+= twl4030-madc-hwmon.o -obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress.o +obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o obj-$(CONFIG_SENSORS_VIA686A) += via686a.o obj-$(CONFIG_SENSORS_VT1211) += vt1211.o diff --git a/drivers/hwmon/vexpress-hwmon.c b/drivers/hwmon/vexpress-hwmon.c new file mode 100644 index 0000000..8ba419d --- /dev/null +++ b/drivers/hwmon/vexpress-hwmon.c @@ -0,0 +1,259 @@ +/* + * 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. + * + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#define DRVNAME "vexpress-hwmon" +#define pr_fmt(fmt) DRVNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct vexpress_hwmon_data { + struct device *hwmon_dev; + struct regmap *reg; +}; + +static ssize_t vexpress_hwmon_label_show(struct device *dev, + struct device_attribute *dev_attr, char *buffer) +{ + const char *label = of_get_property(dev->of_node, "label", NULL); + + return snprintf(buffer, PAGE_SIZE, "%s\n", label); +} + +static ssize_t vexpress_hwmon_u32_show(struct device *dev, + struct device_attribute *dev_attr, char *buffer) +{ + struct vexpress_hwmon_data *data = dev_get_drvdata(dev); + int err; + u32 value; + + err = regmap_read(data->reg, 0, &value); + if (err) + return err; + + return snprintf(buffer, PAGE_SIZE, "%u\n", value / + to_sensor_dev_attr(dev_attr)->index); +} + +static ssize_t vexpress_hwmon_u64_show(struct device *dev, + struct device_attribute *dev_attr, char *buffer) +{ + struct vexpress_hwmon_data *data = dev_get_drvdata(dev); + int err; + u32 value_hi, value_lo; + + err = regmap_read(data->reg, 0, &value_lo); + if (err) + return err; + + err = regmap_read(data->reg, 1, &value_hi); + if (err) + return err; + + return snprintf(buffer, PAGE_SIZE, "%llu\n", + div_u64(((u64)value_hi << 32) | value_lo, + to_sensor_dev_attr(dev_attr)->index)); +} + +static umode_t vexpress_hwmon_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct device_attribute *dev_attr = container_of(attr, + struct device_attribute, attr); + + if (dev_attr->show == vexpress_hwmon_label_show && + !of_get_property(dev->of_node, "label", NULL)) + return 0; + + return attr->mode; +} + +struct vexpress_hwmon_type { + const char *name; + const struct attribute_group **attr_groups; +}; + +#if !defined(CONFIG_REGULATOR_VEXPRESS) +static DEVICE_ATTR(in1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1000); +static struct attribute *vexpress_hwmon_attrs_volt[] = { + &dev_attr_in1_label.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_volt = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_volt, +}; +static struct vexpress_hwmon_type vexpress_hwmon_volt = { + .name = "vexpress_volt", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_volt, + NULL, + }, +}; +#endif + +static DEVICE_ATTR(curr1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1000); +static struct attribute *vexpress_hwmon_attrs_amp[] = { + &dev_attr_curr1_label.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_amp = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_amp, +}; +static struct vexpress_hwmon_type vexpress_hwmon_amp = { + .name = "vexpress_amp", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_amp, + NULL + }, +}; + +static DEVICE_ATTR(temp1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1000); +static struct attribute *vexpress_hwmon_attrs_temp[] = { + &dev_attr_temp1_label.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_temp = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_temp, +}; +static struct vexpress_hwmon_type vexpress_hwmon_temp = { + .name = "vexpress_temp", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_temp, + NULL + }, +}; + +static DEVICE_ATTR(power1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, vexpress_hwmon_u32_show, + NULL, 1); +static struct attribute *vexpress_hwmon_attrs_power[] = { + &dev_attr_power1_label.attr, + &sensor_dev_attr_power1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_power = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_power, +}; +static struct vexpress_hwmon_type vexpress_hwmon_power = { + .name = "vexpress_power", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_power, + NULL + }, +}; + +static DEVICE_ATTR(energy1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); +static SENSOR_DEVICE_ATTR(energy1_input, S_IRUGO, vexpress_hwmon_u64_show, + NULL, 1); +static struct attribute *vexpress_hwmon_attrs_energy[] = { + &dev_attr_energy1_label.attr, + &sensor_dev_attr_energy1_input.dev_attr.attr, + NULL +}; +static struct attribute_group vexpress_hwmon_group_energy = { + .is_visible = vexpress_hwmon_attr_is_visible, + .attrs = vexpress_hwmon_attrs_energy, +}; +static struct vexpress_hwmon_type vexpress_hwmon_energy = { + .name = "vexpress_energy", + .attr_groups = (const struct attribute_group *[]) { + &vexpress_hwmon_group_energy, + NULL + }, +}; + +static const struct of_device_id vexpress_hwmon_of_match[] = { +#if !defined(CONFIG_REGULATOR_VEXPRESS) + { + .compatible = "arm,vexpress-volt", + .data = &vexpress_hwmon_volt, + }, +#endif + { + .compatible = "arm,vexpress-amp", + .data = &vexpress_hwmon_amp, + }, { + .compatible = "arm,vexpress-temp", + .data = &vexpress_hwmon_temp, + }, { + .compatible = "arm,vexpress-power", + .data = &vexpress_hwmon_power, + }, { + .compatible = "arm,vexpress-energy", + .data = &vexpress_hwmon_energy, + }, + {} +}; +MODULE_DEVICE_TABLE(of, vexpress_hwmon_of_match); + +static int vexpress_hwmon_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct vexpress_hwmon_data *data; + const struct vexpress_hwmon_type *type; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + platform_set_drvdata(pdev, data); + + match = of_match_device(vexpress_hwmon_of_match, &pdev->dev); + if (!match) + return -ENODEV; + type = match->data; + + data->reg = devm_regmap_init_vexpress_config(&pdev->dev); + if (IS_ERR(data->reg)) + return PTR_ERR(data->reg); + + data->hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, + type->name, data, type->attr_groups); + + return PTR_ERR_OR_ZERO(data->hwmon_dev); +} + +static struct platform_driver vexpress_hwmon_driver = { + .probe = vexpress_hwmon_probe, + .driver = { + .name = DRVNAME, + .of_match_table = vexpress_hwmon_of_match, + }, +}; + +module_platform_driver(vexpress_hwmon_driver); + +MODULE_AUTHOR("Pawel Moll "); +MODULE_DESCRIPTION("Versatile Express hwmon sensors driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:vexpress-hwmon"); diff --git a/drivers/hwmon/vexpress.c b/drivers/hwmon/vexpress.c deleted file mode 100644 index 8ba419d..0000000 --- a/drivers/hwmon/vexpress.c +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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. - * - * 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. - * - * Copyright (C) 2012 ARM Limited - */ - -#define DRVNAME "vexpress-hwmon" -#define pr_fmt(fmt) DRVNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct vexpress_hwmon_data { - struct device *hwmon_dev; - struct regmap *reg; -}; - -static ssize_t vexpress_hwmon_label_show(struct device *dev, - struct device_attribute *dev_attr, char *buffer) -{ - const char *label = of_get_property(dev->of_node, "label", NULL); - - return snprintf(buffer, PAGE_SIZE, "%s\n", label); -} - -static ssize_t vexpress_hwmon_u32_show(struct device *dev, - struct device_attribute *dev_attr, char *buffer) -{ - struct vexpress_hwmon_data *data = dev_get_drvdata(dev); - int err; - u32 value; - - err = regmap_read(data->reg, 0, &value); - if (err) - return err; - - return snprintf(buffer, PAGE_SIZE, "%u\n", value / - to_sensor_dev_attr(dev_attr)->index); -} - -static ssize_t vexpress_hwmon_u64_show(struct device *dev, - struct device_attribute *dev_attr, char *buffer) -{ - struct vexpress_hwmon_data *data = dev_get_drvdata(dev); - int err; - u32 value_hi, value_lo; - - err = regmap_read(data->reg, 0, &value_lo); - if (err) - return err; - - err = regmap_read(data->reg, 1, &value_hi); - if (err) - return err; - - return snprintf(buffer, PAGE_SIZE, "%llu\n", - div_u64(((u64)value_hi << 32) | value_lo, - to_sensor_dev_attr(dev_attr)->index)); -} - -static umode_t vexpress_hwmon_attr_is_visible(struct kobject *kobj, - struct attribute *attr, int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct device_attribute *dev_attr = container_of(attr, - struct device_attribute, attr); - - if (dev_attr->show == vexpress_hwmon_label_show && - !of_get_property(dev->of_node, "label", NULL)) - return 0; - - return attr->mode; -} - -struct vexpress_hwmon_type { - const char *name; - const struct attribute_group **attr_groups; -}; - -#if !defined(CONFIG_REGULATOR_VEXPRESS) -static DEVICE_ATTR(in1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); -static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, vexpress_hwmon_u32_show, - NULL, 1000); -static struct attribute *vexpress_hwmon_attrs_volt[] = { - &dev_attr_in1_label.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - NULL -}; -static struct attribute_group vexpress_hwmon_group_volt = { - .is_visible = vexpress_hwmon_attr_is_visible, - .attrs = vexpress_hwmon_attrs_volt, -}; -static struct vexpress_hwmon_type vexpress_hwmon_volt = { - .name = "vexpress_volt", - .attr_groups = (const struct attribute_group *[]) { - &vexpress_hwmon_group_volt, - NULL, - }, -}; -#endif - -static DEVICE_ATTR(curr1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); -static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, vexpress_hwmon_u32_show, - NULL, 1000); -static struct attribute *vexpress_hwmon_attrs_amp[] = { - &dev_attr_curr1_label.attr, - &sensor_dev_attr_curr1_input.dev_attr.attr, - NULL -}; -static struct attribute_group vexpress_hwmon_group_amp = { - .is_visible = vexpress_hwmon_attr_is_visible, - .attrs = vexpress_hwmon_attrs_amp, -}; -static struct vexpress_hwmon_type vexpress_hwmon_amp = { - .name = "vexpress_amp", - .attr_groups = (const struct attribute_group *[]) { - &vexpress_hwmon_group_amp, - NULL - }, -}; - -static DEVICE_ATTR(temp1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, vexpress_hwmon_u32_show, - NULL, 1000); -static struct attribute *vexpress_hwmon_attrs_temp[] = { - &dev_attr_temp1_label.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - NULL -}; -static struct attribute_group vexpress_hwmon_group_temp = { - .is_visible = vexpress_hwmon_attr_is_visible, - .attrs = vexpress_hwmon_attrs_temp, -}; -static struct vexpress_hwmon_type vexpress_hwmon_temp = { - .name = "vexpress_temp", - .attr_groups = (const struct attribute_group *[]) { - &vexpress_hwmon_group_temp, - NULL - }, -}; - -static DEVICE_ATTR(power1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); -static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, vexpress_hwmon_u32_show, - NULL, 1); -static struct attribute *vexpress_hwmon_attrs_power[] = { - &dev_attr_power1_label.attr, - &sensor_dev_attr_power1_input.dev_attr.attr, - NULL -}; -static struct attribute_group vexpress_hwmon_group_power = { - .is_visible = vexpress_hwmon_attr_is_visible, - .attrs = vexpress_hwmon_attrs_power, -}; -static struct vexpress_hwmon_type vexpress_hwmon_power = { - .name = "vexpress_power", - .attr_groups = (const struct attribute_group *[]) { - &vexpress_hwmon_group_power, - NULL - }, -}; - -static DEVICE_ATTR(energy1_label, S_IRUGO, vexpress_hwmon_label_show, NULL); -static SENSOR_DEVICE_ATTR(energy1_input, S_IRUGO, vexpress_hwmon_u64_show, - NULL, 1); -static struct attribute *vexpress_hwmon_attrs_energy[] = { - &dev_attr_energy1_label.attr, - &sensor_dev_attr_energy1_input.dev_attr.attr, - NULL -}; -static struct attribute_group vexpress_hwmon_group_energy = { - .is_visible = vexpress_hwmon_attr_is_visible, - .attrs = vexpress_hwmon_attrs_energy, -}; -static struct vexpress_hwmon_type vexpress_hwmon_energy = { - .name = "vexpress_energy", - .attr_groups = (const struct attribute_group *[]) { - &vexpress_hwmon_group_energy, - NULL - }, -}; - -static const struct of_device_id vexpress_hwmon_of_match[] = { -#if !defined(CONFIG_REGULATOR_VEXPRESS) - { - .compatible = "arm,vexpress-volt", - .data = &vexpress_hwmon_volt, - }, -#endif - { - .compatible = "arm,vexpress-amp", - .data = &vexpress_hwmon_amp, - }, { - .compatible = "arm,vexpress-temp", - .data = &vexpress_hwmon_temp, - }, { - .compatible = "arm,vexpress-power", - .data = &vexpress_hwmon_power, - }, { - .compatible = "arm,vexpress-energy", - .data = &vexpress_hwmon_energy, - }, - {} -}; -MODULE_DEVICE_TABLE(of, vexpress_hwmon_of_match); - -static int vexpress_hwmon_probe(struct platform_device *pdev) -{ - const struct of_device_id *match; - struct vexpress_hwmon_data *data; - const struct vexpress_hwmon_type *type; - - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - platform_set_drvdata(pdev, data); - - match = of_match_device(vexpress_hwmon_of_match, &pdev->dev); - if (!match) - return -ENODEV; - type = match->data; - - data->reg = devm_regmap_init_vexpress_config(&pdev->dev); - if (IS_ERR(data->reg)) - return PTR_ERR(data->reg); - - data->hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, - type->name, data, type->attr_groups); - - return PTR_ERR_OR_ZERO(data->hwmon_dev); -} - -static struct platform_driver vexpress_hwmon_driver = { - .probe = vexpress_hwmon_probe, - .driver = { - .name = DRVNAME, - .of_match_table = vexpress_hwmon_of_match, - }, -}; - -module_platform_driver(vexpress_hwmon_driver); - -MODULE_AUTHOR("Pawel Moll "); -MODULE_DESCRIPTION("Versatile Express hwmon sensors driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:vexpress-hwmon"); -- cgit v0.10.2 From df922703574ebe9035045f7c7242a0ec0e11b980 Mon Sep 17 00:00:00 2001 From: Mike Looijmans Date: Fri, 15 Jan 2016 10:54:59 +0100 Subject: hwmon: Add LTC2990 sensor driver This adds support for the Linear Technology LTC2990 I2C System Monitor. The LTC2990 supports a combination of voltage, current and temperature monitoring. This driver currently only supports reading two currents by measuring two differential voltages across series resistors, in addition to the Vcc supply voltage and internal temperature. This is sufficient to support the Topic Miami SOM which uses this chip to monitor the currents flowing into the FPGA and the CPU parts. Signed-off-by: Mike Looijmans Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/ltc2990 b/Documentation/hwmon/ltc2990 new file mode 100644 index 0000000..c25211e --- /dev/null +++ b/Documentation/hwmon/ltc2990 @@ -0,0 +1,43 @@ +Kernel driver ltc2990 +===================== + +Supported chips: + * Linear Technology LTC2990 + Prefix: 'ltc2990' + Addresses scanned: - + Datasheet: http://www.linear.com/product/ltc2990 + +Author: Mike Looijmans + + +Description +----------- + +LTC2990 is a Quad I2C Voltage, Current and Temperature Monitor. +The chip's inputs can measure 4 voltages, or two inputs together (1+2 and 3+4) +can be combined to measure a differential voltage, which is typically used to +measure current through a series resistor, or a temperature. + +This driver currently uses the 2x differential mode only. In order to support +other modes, the driver will need to be expanded. + + +Usage Notes +----------- + +This driver does not probe for PMBus devices. You will have to instantiate +devices explicitly. + + +Sysfs attributes +---------------- + +The "curr*_input" measurements actually report the voltage drop across the +input pins in microvolts. This is equivalent to the current through a 1mOhm +sense resistor. Divide the reported value by the actual sense resistor value +in mOhm to get the actual value. + +in0_input Voltage at Vcc pin in millivolt (range 2.5V to 5V) +temp1_input Internal chip temperature in millidegrees Celcius +curr1_input Current in mA across v1-v2 assuming a 1mOhm sense resistor. +curr2_input Current in mA across v3-v4 assuming a 1mOhm sense resistor. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 60fb80b..852c8a8 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -685,6 +685,20 @@ config SENSORS_LTC2945 This driver can also be built as a module. If so, the module will be called ltc2945. +config SENSORS_LTC2990 + tristate "Linear Technology LTC2990 (current monitoring mode only)" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC2990 + I2C System Monitor. The LTC2990 supports a combination of voltage, + current and temperature monitoring, but in addition to the Vcc supply + voltage and chip temperature, this driver currently only supports + reading two currents by measuring two differential voltages across + series resistors. + + This driver can also be built as a module. If so, the module will + be called ltc2990. + config SENSORS_LTC4151 tristate "Linear Technology LTC4151" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index cfc0971..a7ecaf2 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_LM95234) += lm95234.o obj-$(CONFIG_SENSORS_LM95241) += lm95241.o obj-$(CONFIG_SENSORS_LM95245) += lm95245.o obj-$(CONFIG_SENSORS_LTC2945) += ltc2945.o +obj-$(CONFIG_SENSORS_LTC2990) += ltc2990.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o diff --git a/drivers/hwmon/ltc2990.c b/drivers/hwmon/ltc2990.c new file mode 100644 index 0000000..8f8fe05 --- /dev/null +++ b/drivers/hwmon/ltc2990.c @@ -0,0 +1,161 @@ +/* + * Driver for Linear Technology LTC2990 power monitor + * + * Copyright (C) 2014 Topic Embedded Products + * Author: Mike Looijmans + * + * License: GPLv2 + * + * This driver assumes the chip is wired as a dual current monitor, and + * reports the voltage drop across two series resistors. It also reports + * the chip's internal temperature and Vcc power supply voltage. + */ + +#include +#include +#include +#include +#include +#include + +#define LTC2990_STATUS 0x00 +#define LTC2990_CONTROL 0x01 +#define LTC2990_TRIGGER 0x02 +#define LTC2990_TINT_MSB 0x04 +#define LTC2990_V1_MSB 0x06 +#define LTC2990_V2_MSB 0x08 +#define LTC2990_V3_MSB 0x0A +#define LTC2990_V4_MSB 0x0C +#define LTC2990_VCC_MSB 0x0E + +#define LTC2990_CONTROL_KELVIN BIT(7) +#define LTC2990_CONTROL_SINGLE BIT(6) +#define LTC2990_CONTROL_MEASURE_ALL (0x3 << 3) +#define LTC2990_CONTROL_MODE_CURRENT 0x06 +#define LTC2990_CONTROL_MODE_VOLTAGE 0x07 + +/* convert raw register value to sign-extended integer in 16-bit range */ +static int ltc2990_voltage_to_int(int raw) +{ + if (raw & BIT(14)) + return -(0x4000 - (raw & 0x3FFF)) << 2; + else + return (raw & 0x3FFF) << 2; +} + +/* Return the converted value from the given register in uV or mC */ +static int ltc2990_get_value(struct i2c_client *i2c, u8 reg, int *result) +{ + int val; + + val = i2c_smbus_read_word_swapped(i2c, reg); + if (unlikely(val < 0)) + return val; + + switch (reg) { + case LTC2990_TINT_MSB: + /* internal temp, 0.0625 degrees/LSB, 13-bit */ + val = (val & 0x1FFF) << 3; + *result = (val * 1000) >> 7; + break; + case LTC2990_V1_MSB: + case LTC2990_V3_MSB: + /* Vx-Vy, 19.42uV/LSB. Depends on mode. */ + *result = ltc2990_voltage_to_int(val) * 1942 / (4 * 100); + break; + case LTC2990_VCC_MSB: + /* Vcc, 305.18μV/LSB, 2.5V offset */ + *result = (ltc2990_voltage_to_int(val) * 30518 / + (4 * 100 * 1000)) + 2500; + break; + default: + return -EINVAL; /* won't happen, keep compiler happy */ + } + + return 0; +} + +static ssize_t ltc2990_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int value; + int ret; + + ret = ltc2990_get_value(dev_get_drvdata(dev), attr->index, &value); + if (unlikely(ret < 0)) + return ret; + + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_TINT_MSB); +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_V1_MSB); +static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_V3_MSB); +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, ltc2990_show_value, NULL, + LTC2990_VCC_MSB); + +static struct attribute *ltc2990_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_in0_input.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ltc2990); + +static int ltc2990_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret; + struct device *hwmon_dev; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + /* Setup continuous mode, current monitor */ + ret = i2c_smbus_write_byte_data(i2c, LTC2990_CONTROL, + LTC2990_CONTROL_MEASURE_ALL | + LTC2990_CONTROL_MODE_CURRENT); + if (ret < 0) { + dev_err(&i2c->dev, "Error: Failed to set control mode.\n"); + return ret; + } + /* Trigger once to start continuous conversion */ + ret = i2c_smbus_write_byte_data(i2c, LTC2990_TRIGGER, 1); + if (ret < 0) { + dev_err(&i2c->dev, "Error: Failed to start acquisition.\n"); + return ret; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(&i2c->dev, + i2c->name, + i2c, + ltc2990_groups); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id ltc2990_i2c_id[] = { + { "ltc2990", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc2990_i2c_id); + +static struct i2c_driver ltc2990_i2c_driver = { + .driver = { + .name = "ltc2990", + }, + .probe = ltc2990_i2c_probe, + .id_table = ltc2990_i2c_id, +}; + +module_i2c_driver(ltc2990_i2c_driver); + +MODULE_DESCRIPTION("LTC2990 Sensor Driver"); +MODULE_AUTHOR("Topic Embedded Products"); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From b92fe9e3379c8dc885eefb254c3da94c13cc1b6d Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 16 Feb 2016 10:30:53 +0530 Subject: hwmon: (iio_hwmon) Allow the driver to accept hypen in device tree node names Currently the driver calls hwmon_device_register_with_groups which does not accept hypen in node name and returns EINVAL. Use of hypen in device tree node name results in probe failure., however use of hypen in device tree node name is perfectly acceptable. Change this by allocating a duplicate managed string, replacing hypen with underscore and then calling hwmon_device_register_with_groups. This allows the use of hypen in device tree node name while maintaining backwards compatibility and preventing any possible regressions with user space. Signed-off-by: Sanchayan Maity Signed-off-by: Guenter Roeck diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c index 17ae2eb..b550ba5 100644 --- a/drivers/hwmon/iio_hwmon.c +++ b/drivers/hwmon/iio_hwmon.c @@ -67,6 +67,7 @@ static int iio_hwmon_probe(struct platform_device *pdev) enum iio_chan_type type; struct iio_channel *channels; const char *name = "iio_hwmon"; + char *sname; if (dev->of_node && dev->of_node->name) name = dev->of_node->name; @@ -144,7 +145,15 @@ static int iio_hwmon_probe(struct platform_device *pdev) st->attr_group.attrs = st->attrs; st->groups[0] = &st->attr_group; - st->hwmon_dev = hwmon_device_register_with_groups(dev, name, st, + + sname = devm_kstrdup(dev, name, GFP_KERNEL); + if (!sname) { + ret = -ENOMEM; + goto error_release_channels; + } + + strreplace(sname, '-', '_'); + st->hwmon_dev = hwmon_device_register_with_groups(dev, sname, st, st->groups); if (IS_ERR(st->hwmon_dev)) { ret = PTR_ERR(st->hwmon_dev); -- cgit v0.10.2 From 0b452ccca6f9e55fc727a461f7a778bebbefff0b Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 16 Feb 2016 10:30:54 +0530 Subject: ARM: dts: Change iio_hwmon nodes to use hypen in node names Change iio_hwmon nodes to use hypen in node names instead of underscore. Signed-off-by: Sanchayan Maity Signed-off-by: Guenter Roeck diff --git a/Documentation/devicetree/bindings/iio/iio-bindings.txt b/Documentation/devicetree/bindings/iio/iio-bindings.txt index 0b447d9..68d6f8c 100644 --- a/Documentation/devicetree/bindings/iio/iio-bindings.txt +++ b/Documentation/devicetree/bindings/iio/iio-bindings.txt @@ -82,7 +82,7 @@ vdd channel is connected to output 0 of the &ref device. ... - iio_hwmon { + iio-hwmon { compatible = "iio-hwmon"; io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 3>, <&adc 4>, <&adc 5>, diff --git a/arch/arm/boot/dts/imx23.dtsi b/arch/arm/boot/dts/imx23.dtsi index 1c6c075..302d116 100644 --- a/arch/arm/boot/dts/imx23.dtsi +++ b/arch/arm/boot/dts/imx23.dtsi @@ -569,7 +569,7 @@ }; }; - iio_hwmon { + iio-hwmon { compatible = "iio-hwmon"; io-channels = <&lradc 8>; }; diff --git a/arch/arm/boot/dts/imx28.dtsi b/arch/arm/boot/dts/imx28.dtsi index fae7b90..f637ec9 100644 --- a/arch/arm/boot/dts/imx28.dtsi +++ b/arch/arm/boot/dts/imx28.dtsi @@ -1256,7 +1256,7 @@ }; }; - iio_hwmon { + iio-hwmon { compatible = "iio-hwmon"; io-channels = <&lradc 8>; }; -- cgit v0.10.2 From 0aa8a99680cf2a7bc216b379a9fc257893e606cb Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 16 Feb 2016 10:30:55 +0530 Subject: ARM: dts: vfxxx: Add iio_hwmon node for ADC temperature channel Add iio-hwmon node to expose the temperature channel on Vybrid as hardware monitor device using the iio_hwmon driver. Signed-off-by: Sanchayan Maity Acked-by: Stefan Agner Signed-off-by: Guenter Roeck diff --git a/arch/arm/boot/dts/vfxxx.dtsi b/arch/arm/boot/dts/vfxxx.dtsi index a9ceb5b..4539f8d 100644 --- a/arch/arm/boot/dts/vfxxx.dtsi +++ b/arch/arm/boot/dts/vfxxx.dtsi @@ -629,5 +629,10 @@ status = "disabled"; }; }; + + iio-hwmon { + compatible = "iio-hwmon"; + io-channels = <&adc0 16>, <&adc1 16>; + }; }; }; -- cgit v0.10.2 From 92417dad642c4002ae0b888514088a9aa8b6f9d0 Mon Sep 17 00:00:00 2001 From: Masanari Iida Date: Sun, 28 Feb 2016 12:57:57 +0900 Subject: Doc: hwmon: Fix typo "montoring" in hwmon This patch fix typos found in files within Documentation/hwmon. Signed-off-by: Masanari Iida Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/adm1275 b/Documentation/hwmon/adm1275 index d697229..04e8e4a 100644 --- a/Documentation/hwmon/adm1275 +++ b/Documentation/hwmon/adm1275 @@ -25,7 +25,7 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for Analog Devices ADM1075, ADM1275, +This driver supports hardware monitoring for Analog Devices ADM1075, ADM1275, ADM1276, ADM1293, and ADM1294 Hot-Swap Controller and Digital Power Monitors. ADM1075, ADM1275, ADM1276, ADM1293, and ADM1294 are hot-swap controllers that diff --git a/Documentation/hwmon/lm25066 b/Documentation/hwmon/lm25066 index b34c3de..2cb20eb 100644 --- a/Documentation/hwmon/lm25066 +++ b/Documentation/hwmon/lm25066 @@ -36,7 +36,7 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for National Semiconductor / TI LM25056, +This driver supports hardware monitoring for National Semiconductor / TI LM25056, LM25063, LM25066, LM5064, and LM5066 Power Management, Monitoring, Control, and Protection ICs. diff --git a/Documentation/hwmon/max16064 b/Documentation/hwmon/max16064 index d59cc78..265370f 100644 --- a/Documentation/hwmon/max16064 +++ b/Documentation/hwmon/max16064 @@ -13,7 +13,7 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for Maxim MAX16064 Quad Power-Supply +This driver supports hardware monitoring for Maxim MAX16064 Quad Power-Supply Controller with Active-Voltage Output Control and PMBus Interface. The driver is a client driver to the core PMBus driver. diff --git a/Documentation/hwmon/max34440 b/Documentation/hwmon/max34440 index 37cbf47..f5b1fca 100644 --- a/Documentation/hwmon/max34440 +++ b/Documentation/hwmon/max34440 @@ -33,7 +33,7 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for Maxim MAX34440 PMBus 6-Channel +This driver supports hardware monitoring for Maxim MAX34440 PMBus 6-Channel Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply Manager and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data Logger. It also supports the MAX34460 and MAX34461 PMBus Voltage Monitor & Sequencers. diff --git a/Documentation/hwmon/max8688 b/Documentation/hwmon/max8688 index e780786..ca233be 100644 --- a/Documentation/hwmon/max8688 +++ b/Documentation/hwmon/max8688 @@ -13,7 +13,7 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for Maxim MAX8688 Digital Power-Supply +This driver supports hardware monitoring for Maxim MAX8688 Digital Power-Supply Controller/Monitor with PMBus Interface. The driver is a client driver to the core PMBus driver. Please see diff --git a/Documentation/hwmon/pmbus b/Documentation/hwmon/pmbus index b397675..dfd9c65 100644 --- a/Documentation/hwmon/pmbus +++ b/Documentation/hwmon/pmbus @@ -43,7 +43,7 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for various PMBus compliant devices. +This driver supports hardware monitoring for various PMBus compliant devices. It supports voltage, current, power, and temperature sensors as supported by the device. diff --git a/Documentation/hwmon/zl6100 b/Documentation/hwmon/zl6100 index 33908a4..477a94b 100644 --- a/Documentation/hwmon/zl6100 +++ b/Documentation/hwmon/zl6100 @@ -60,7 +60,7 @@ Author: Guenter Roeck Description ----------- -This driver supports hardware montoring for Intersil / Zilker Labs ZL6100 and +This driver supports hardware monitoring for Intersil / Zilker Labs ZL6100 and compatible digital DC-DC controllers. The driver is a client driver to the core PMBus driver. Please see -- cgit v0.10.2 From 54ce3a0d801142c96935122736a46c08d15d83b5 Mon Sep 17 00:00:00 2001 From: Joseph McNally Date: Sun, 28 Feb 2016 22:31:23 +0000 Subject: hwmon: (ntc_thermistor) Add support for ncpXXxh103 This patch adds support for the Murata NCP15XH103 thermistor series. Signed-off-by: Joseph McNally Signed-off-by: Guenter Roeck diff --git a/Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt b/Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt index a04a80f..c3b9c4c 100644 --- a/Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt +++ b/Documentation/devicetree/bindings/hwmon/ntc_thermistor.txt @@ -10,6 +10,7 @@ Requires node properties: "murata,ncp03wb473" "murata,ncp15wl333" "murata,ncp03wf104" + "murata,ncp15xh103" /* Usage of vendor name "ntc" is deprecated */ "ntc,ncp15wb473" diff --git a/Documentation/hwmon/ntc_thermistor b/Documentation/hwmon/ntc_thermistor index 1d4cc84..8b9ff23 100644 --- a/Documentation/hwmon/ntc_thermistor +++ b/Documentation/hwmon/ntc_thermistor @@ -3,9 +3,9 @@ Kernel driver ntc_thermistor Supported thermistors from Murata: * Murata NTC Thermistors NCP15WB473, NCP18WB473, NCP21WB473, NCP03WB473, - NCP15WL333, NCP03WF104 + NCP15WL333, NCP03WF104, NCP15XH103 Prefixes: 'ncp15wb473', 'ncp18wb473', 'ncp21wb473', 'ncp03wb473', - 'ncp15wl333', 'ncp03wf104' + 'ncp15wl333', 'ncp03wf104', 'ncp15xh103' Datasheet: Publicly available at Murata Supported thermistors from EPCOS: diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 852c8a8..7a437ce 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1141,7 +1141,7 @@ config SENSORS_NTC_THERMISTOR Currently, this driver supports NCP15WB473, NCP18WB473, NCP21WB473, NCP03WB473, NCP15WL333, - and NCP03WF104 from Murata and B57330V2103 from EPCOS. + NCP03WF104 and NCP15XH103 from Murata and B57330V2103 from EPCOS. This driver can also be built as a module. If so, the module will be called ntc-thermistor. diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index feed306..faa6e8d 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -54,6 +54,7 @@ static const struct platform_device_id ntc_thermistor_id[] = { { "ncp15wl333", TYPE_NCPXXWL333 }, { "b57330v2103", TYPE_B57330V2103}, { "ncp03wf104", TYPE_NCPXXWF104 }, + { "ncp15xh103", TYPE_NCPXXXH103 }, { }, }; @@ -173,6 +174,43 @@ static const struct ntc_compensation ncpXXwf104[] = { { .temp_c = 125, .ohm = 2522 }, }; +static const struct ntc_compensation ncpXXxh103[] = { + { .temp_c = -40, .ohm = 247565 }, + { .temp_c = -35, .ohm = 181742 }, + { .temp_c = -30, .ohm = 135128 }, + { .temp_c = -25, .ohm = 101678 }, + { .temp_c = -20, .ohm = 77373 }, + { .temp_c = -15, .ohm = 59504 }, + { .temp_c = -10, .ohm = 46222 }, + { .temp_c = -5, .ohm = 36244 }, + { .temp_c = 0, .ohm = 28674 }, + { .temp_c = 5, .ohm = 22878 }, + { .temp_c = 10, .ohm = 18399 }, + { .temp_c = 15, .ohm = 14910 }, + { .temp_c = 20, .ohm = 12169 }, + { .temp_c = 25, .ohm = 10000 }, + { .temp_c = 30, .ohm = 8271 }, + { .temp_c = 35, .ohm = 6883 }, + { .temp_c = 40, .ohm = 5762 }, + { .temp_c = 45, .ohm = 4851 }, + { .temp_c = 50, .ohm = 4105 }, + { .temp_c = 55, .ohm = 3492 }, + { .temp_c = 60, .ohm = 2985 }, + { .temp_c = 65, .ohm = 2563 }, + { .temp_c = 70, .ohm = 2211 }, + { .temp_c = 75, .ohm = 1915 }, + { .temp_c = 80, .ohm = 1666 }, + { .temp_c = 85, .ohm = 1454 }, + { .temp_c = 90, .ohm = 1275 }, + { .temp_c = 95, .ohm = 1121 }, + { .temp_c = 100, .ohm = 990 }, + { .temp_c = 105, .ohm = 876 }, + { .temp_c = 110, .ohm = 779 }, + { .temp_c = 115, .ohm = 694 }, + { .temp_c = 120, .ohm = 620 }, + { .temp_c = 125, .ohm = 556 }, +}; + /* * The following compensation table is from the specification of EPCOS NTC * Thermistors Datasheet @@ -260,6 +298,8 @@ static const struct of_device_id ntc_match[] = { .data = &ntc_thermistor_id[5]}, { .compatible = "murata,ncp03wf104", .data = &ntc_thermistor_id[6] }, + { .compatible = "murata,ncp15xh103", + .data = &ntc_thermistor_id[7] }, /* Usage of vendor name "ntc" is deprecated */ { .compatible = "ntc,ncp15wb473", @@ -609,6 +649,10 @@ static int ntc_thermistor_probe(struct platform_device *pdev) data->comp = ncpXXwf104; data->n_comp = ARRAY_SIZE(ncpXXwf104); break; + case TYPE_NCPXXXH103: + data->comp = ncpXXxh103; + data->n_comp = ARRAY_SIZE(ncpXXxh103); + break; default: dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n", pdev_id->driver_data, pdev_id->name); diff --git a/include/linux/platform_data/ntc_thermistor.h b/include/linux/platform_data/ntc_thermistor.h index aed1705..698d0d5 100644 --- a/include/linux/platform_data/ntc_thermistor.h +++ b/include/linux/platform_data/ntc_thermistor.h @@ -28,6 +28,7 @@ enum ntc_thermistor_type { TYPE_NCPXXWL333, TYPE_B57330V2103, TYPE_NCPXXWF104, + TYPE_NCPXXXH103, }; struct ntc_thermistor_platform_data { -- cgit v0.10.2 From 709066acdd12c3312c94ebccc37630932e381949 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 5 Jul 2015 11:04:56 -0700 Subject: hwmon: (adm1275) Add support for ADM1278 ADM1278 is mostly compatible to other chips of the same series. Besides the usual difference in coefficients, it supports a temperature sensor, and it can measure both input and output voltage at the same time. Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/adm1275 b/Documentation/hwmon/adm1275 index 04e8e4a..791bc0b 100644 --- a/Documentation/hwmon/adm1275 +++ b/Documentation/hwmon/adm1275 @@ -14,6 +14,10 @@ Supported chips: Prefix: 'adm1276' Addresses scanned: - Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1276.pdf + * Analog Devices ADM1278 + Prefix: 'adm1278' + Addresses scanned: - + Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1278.pdf * Analog Devices ADM1293/ADM1294 Prefix: 'adm1293', 'adm1294' Addresses scanned: - @@ -26,12 +30,14 @@ Description ----------- This driver supports hardware monitoring for Analog Devices ADM1075, ADM1275, -ADM1276, ADM1293, and ADM1294 Hot-Swap Controller and Digital Power Monitors. +ADM1276, ADM1278, ADM1293, and ADM1294 Hot-Swap Controller and Digital +Power Monitors. -ADM1075, ADM1275, ADM1276, ADM1293, and ADM1294 are hot-swap controllers that -allow a circuit board to be removed from or inserted into a live backplane. -They also feature current and voltage readback via an integrated 12 -bit analog-to-digital converter (ADC), accessed using a PMBus interface. +ADM1075, ADM1275, ADM1276, ADM1278, ADM1293, and ADM1294 are hot-swap +controllers that allow a circuit board to be removed from or inserted into +a live backplane. They also feature current and voltage readback via an +integrated 12 bit analog-to-digital converter (ADC), accessed using a +PMBus interface. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus for details on PMBus client drivers. @@ -96,3 +102,14 @@ power1_reset_history Write any value to reset history. Power attributes are supported on ADM1075, ADM1276, ADM1293, and ADM1294. + +temp1_input Chip temperature. + Temperature attributes are only available on ADM1278. +temp1_max Maximum chip temperature. +temp1_max_alarm Temperature alarm. +temp1_crit Critical chip temperature. +temp1_crit_alarm Critical temperature high alarm. +temp1_highest Highest observed temperature. +temp1_reset_history Write any value to reset history. + + Temperature attributes are supported on ADM1278. diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 7e5cc3d..054d3d8 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -31,8 +31,8 @@ config SENSORS_ADM1275 default n help If you say yes here you get hardware monitoring support for Analog - Devices ADM1075, ADM1275, ADM1276, ADM1293, and ADM1294 Hot-Swap - Controller and Digital Power Monitors. + Devices ADM1075, ADM1275, ADM1276, ADM1278, ADM1293, and ADM1294 + Hot-Swap Controller and Digital Power Monitors. This driver can also be built as a module. If so, the module will be called adm1275. diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 188af4c..3baa4f4a 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -24,7 +24,7 @@ #include #include "pmbus.h" -enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 }; +enum chips { adm1075, adm1275, adm1276, adm1278, adm1293, adm1294 }; #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) @@ -41,6 +41,10 @@ enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 }; #define ADM1075_IRANGE_25 BIT(3) #define ADM1075_IRANGE_MASK (BIT(3) | BIT(4)) +#define ADM1278_TEMP1_EN BIT(3) +#define ADM1278_VIN_EN BIT(2) +#define ADM1278_VOUT_EN BIT(1) + #define ADM1293_IRANGE_25 0 #define ADM1293_IRANGE_50 BIT(6) #define ADM1293_IRANGE_100 BIT(7) @@ -54,6 +58,7 @@ enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 }; #define ADM1293_VAUX_EN BIT(1) +#define ADM1278_PEAK_TEMP 0xd7 #define ADM1275_IOUT_WARN2_LIMIT 0xd7 #define ADM1275_DEVICE_CONFIG 0xd8 @@ -80,6 +85,7 @@ struct adm1275_data { bool have_iout_min; bool have_pin_min; bool have_pin_max; + bool have_temp_max; struct pmbus_driver_info info; }; @@ -113,6 +119,13 @@ static const struct coefficients adm1276_coefficients[] = { [4] = { 2115, 0, -1 }, /* power, vrange not set */ }; +static const struct coefficients adm1278_coefficients[] = { + [0] = { 19599, 0, -2 }, /* voltage */ + [1] = { 800, 20475, -1 }, /* current */ + [2] = { 6123, 0, -2 }, /* power */ + [3] = { 42, 31880, -1 }, /* temperature */ +}; + static const struct coefficients adm1293_coefficients[] = { [0] = { 3333, -1, 0 }, /* voltage, vrange 1.2V */ [1] = { 5552, -5, -1 }, /* voltage, vrange 7.4V */ @@ -196,6 +209,11 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) return -ENXIO; ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN); break; + case PMBUS_VIRT_READ_TEMP_MAX: + if (!data->have_temp_max) + return -ENXIO; + ret = pmbus_read_word_data(client, 0, ADM1278_PEAK_TEMP); + break; case PMBUS_VIRT_RESET_IOUT_HISTORY: case PMBUS_VIRT_RESET_VOUT_HISTORY: case PMBUS_VIRT_RESET_VIN_HISTORY: @@ -204,6 +222,10 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) if (!data->have_pin_max) return -ENXIO; break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + if (!data->have_temp_max) + return -ENXIO; + break; default: ret = -ENODATA; break; @@ -245,6 +267,9 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg, ret = pmbus_write_word_data(client, 0, ADM1293_PIN_MIN, 0); break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1278_PEAK_TEMP, 0); + break; default: ret = -ENODATA; break; @@ -312,6 +337,7 @@ static const struct i2c_device_id adm1275_id[] = { { "adm1075", adm1075 }, { "adm1275", adm1275 }, { "adm1276", adm1276 }, + { "adm1278", adm1278 }, { "adm1293", adm1293 }, { "adm1294", adm1294 }, { } @@ -329,6 +355,7 @@ static int adm1275_probe(struct i2c_client *client, const struct i2c_device_id *mid; const struct coefficients *coefficients; int vindex = -1, voindex = -1, cindex = -1, pindex = -1; + int tindex = -1; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA @@ -386,6 +413,7 @@ static int adm1275_probe(struct i2c_client *client, info->format[PSC_VOLTAGE_OUT] = direct; info->format[PSC_CURRENT_OUT] = direct; info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; info->read_word_data = adm1275_read_word_data; @@ -460,6 +488,27 @@ static int adm1275_probe(struct i2c_client *client, info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; break; + case adm1278: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + + coefficients = adm1278_coefficients; + vindex = 0; + cindex = 1; + pindex = 2; + tindex = 3; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1278_TEMP1_EN) + info->func[0] |= + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + if (config & ADM1278_VOUT_EN) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; case adm1293: case adm1294: data->have_iout_min = true; @@ -537,6 +586,11 @@ static int adm1275_probe(struct i2c_client *client, info->b[PSC_POWER] = coefficients[pindex].b; info->R[PSC_POWER] = coefficients[pindex].R; } + if (tindex >= 0) { + info->m[PSC_TEMPERATURE] = coefficients[tindex].m; + info->b[PSC_TEMPERATURE] = coefficients[tindex].b; + info->R[PSC_TEMPERATURE] = coefficients[tindex].R; + } return pmbus_do_probe(client, id, info); } -- cgit v0.10.2 From 8d801243192f4dca693f9ff0c7953ff2ce01164d Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Sat, 5 Mar 2016 15:34:55 +0000 Subject: hwmon: Define binding for the nsa320-hwmon driver Define a binding for the hardware monitoring chip present in the Zyxel NSA-320 and some of the other Zyxel NAS devices. Signed-off-by: Adam Baker Acked-by: Rob Herring [groeck: Fixed whitespace error] Signed-off-by: Guenter Roeck diff --git a/Documentation/devicetree/bindings/hwmon/nsa320-mcu.txt b/Documentation/devicetree/bindings/hwmon/nsa320-mcu.txt new file mode 100644 index 0000000..0863e06 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/nsa320-mcu.txt @@ -0,0 +1,20 @@ +Bindings for the fan / temperature monitor microcontroller used on +the Zyxel NSA 320 and several subsequent models. + +Required properties: +- compatible : "zyxel,nsa320-mcu" +- data-gpios : The GPIO pin connected to the data line on the MCU +- clk-gpios : The GPIO pin connected to the clock line on the MCU +- act-gpios : The GPIO pin connected to the active line on the MCU + +Example: + + hwmon { + compatible = "zyxel,nsa320-mcu"; + pinctrl-0 = <&pmx_mcu_data &pmx_mcu_clk &pmx_mcu_act>; + pinctrl-names = "default"; + + data-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; + clk-gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>; + act-gpios = <&gpio0 17 GPIO_ACTIVE_LOW>; + }; -- cgit v0.10.2 From 630300d5fcb6ee9c32c75d8b576c100fbb794159 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Sat, 5 Mar 2016 15:34:56 +0000 Subject: hwmon: Create an NSA320 hardware monitoring driver Create a driver to support the hardware monitoring chip present in the Zyxel NSA320 and some of the other Zyxel NAS devices. The driver reads fan speed and temperature from a suitably pre-programmed MCU on the device. Signed-off-by: Adam Baker [groeck: Dropped .owner field initialization] Signed-off-by: Guenter Roeck diff --git a/Documentation/hwmon/nsa320 b/Documentation/hwmon/nsa320 new file mode 100644 index 0000000..fdbd694 --- /dev/null +++ b/Documentation/hwmon/nsa320 @@ -0,0 +1,53 @@ +Kernel driver nsa320_hwmon +========================== + +Supported chips: + * Holtek HT46R065 microcontroller with onboard firmware that configures + it to act as a hardware monitor. + Prefix: 'nsa320' + Addresses scanned: none + Datasheet: Not available, driver was reverse engineered based upon the + Zyxel kernel source + +Author: + Adam Baker + +Description +----------- + +This chip is known to be used in the Zyxel NSA320 and NSA325 NAS Units and +also in some variants of the NSA310 but the driver has only been tested +on the NSA320. In all of these devices it is connected to the same 3 GPIO +lines which are used to provide chip select, clock and data lines. The +interface behaves similarly to SPI but at much lower speeds than are normally +used for SPI. + +Following each chip select pulse the chip will generate a single 32 bit word +that contains 0x55 as a marker to indicate that data is being read correctly, +followed by an 8 bit fan speed in 100s of RPM and a 16 bit temperature in +tenths of a degree. + + +sysfs-Interface +--------------- + +temp1_input - temperature input +fan1_input - fan speed + +Notes +----- + +The access timings used in the driver are the same as used in the Zyxel +provided kernel. Testing has shown that if the delay between chip select and +the first clock pulse is reduced from 100 ms to just under 10ms then the chip +will not produce any output. If the duration of either phase of the clock +is reduced from 100 us to less than 15 us then data pulses are likely to be +read twice corrupting the output. The above analysis is based upon a sample +of one unit but suggests that the Zyxel provided delay values include a +reasonable tolerance. + +The driver incorporates a limit that it will not check for updated values +faster than once a second. This is because the hardware takes a relatively long +time to read the data from the device and when it does it reads both temp and +fan speed. As the most likely case for two accesses in quick succession is +to read both of these values avoiding a second read delay is desirable. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7a437ce..5c2d13a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1190,6 +1190,21 @@ config SENSORS_NCT7904 This driver can also be built as a module. If so, the module will be called nct7904. +config SENSORS_NSA320 + tristate "ZyXEL NSA320 and compatible fan speed and temperature sensors" + depends on GPIOLIB && OF + depends on MACH_KIRKWOOD || COMPILE_TEST + help + If you say yes here you get support for hardware monitoring + for the ZyXEL NSA320 Media Server and other compatible devices + (probably the NSA325 and some NSA310 variants). + + The sensor data is taken from a Holtek HT46R065 microcontroller + connected to GPIO lines. + + This driver can also be built as a module. If so, the module + will be called nsa320-hwmon. + config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index a7ecaf2..58cc3ac 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -124,6 +124,7 @@ obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o +obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o diff --git a/drivers/hwmon/nsa320-hwmon.c b/drivers/hwmon/nsa320-hwmon.c new file mode 100644 index 0000000..0517a2657 --- /dev/null +++ b/drivers/hwmon/nsa320-hwmon.c @@ -0,0 +1,215 @@ +/* + * drivers/hwmon/nsa320-hwmon.c + * + * ZyXEL NSA320 Media Servers + * hardware monitoring + * + * Copyright (C) 2016 Adam Baker + * based on a board file driver + * Copyright (C) 2012 Peter Schildmann + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Tests for error return values rely upon this value being < 0x80 */ +#define MAGIC_NUMBER 0x55 + +/* + * The Zyxel hwmon MCU is a Holtek HT46R065 that is factory programmed + * to perform temperature and fan speed monitoring. It is read by taking + * the active pin low. The 32 bit output word is then clocked onto the + * data line. The MSB of the data word is a magic nuber to indicate it + * has been read correctly, the next byte is the fan speed (in hundreds + * of RPM) and the last two bytes are the temperature (in tenths of a + * degree) + */ + +struct nsa320_hwmon { + struct mutex update_lock; /* lock GPIO operations */ + unsigned long last_updated; /* jiffies */ + unsigned long mcu_data; + struct gpio_desc *act; + struct gpio_desc *clk; + struct gpio_desc *data; +}; + +enum nsa320_inputs { + NSA320_TEMP = 0, + NSA320_FAN = 1, +}; + +static const char * const nsa320_input_names[] = { + [NSA320_TEMP] = "System Temperature", + [NSA320_FAN] = "Chassis Fan", +}; + +/* + * Although this protocol looks similar to SPI the long delay + * between the active (aka chip select) signal and the shorter + * delay between clock pulses are needed for reliable operation. + * The delays provided are taken from the manufacturer kernel, + * testing suggest they probably incorporate a reasonable safety + * margin. (The single device tested became unreliable if the + * delay was reduced to 1/10th of this value.) + */ +static s32 nsa320_hwmon_update(struct device *dev) +{ + u32 mcu_data; + u32 mask; + struct nsa320_hwmon *hwmon = dev_get_drvdata(dev); + + mutex_lock(&hwmon->update_lock); + + mcu_data = hwmon->mcu_data; + + if (time_after(jiffies, hwmon->last_updated + HZ) || mcu_data == 0) { + gpiod_set_value(hwmon->act, 1); + msleep(100); + + mcu_data = 0; + for (mask = BIT(31); mask; mask >>= 1) { + gpiod_set_value(hwmon->clk, 0); + usleep_range(100, 200); + gpiod_set_value(hwmon->clk, 1); + usleep_range(100, 200); + if (gpiod_get_value(hwmon->data)) + mcu_data |= mask; + } + + gpiod_set_value(hwmon->act, 0); + dev_dbg(dev, "Read raw MCU data %08x\n", mcu_data); + + if ((mcu_data >> 24) != MAGIC_NUMBER) { + dev_dbg(dev, "Read invalid MCU data %08x\n", mcu_data); + mcu_data = -EIO; + } else { + hwmon->mcu_data = mcu_data; + hwmon->last_updated = jiffies; + } + } + + mutex_unlock(&hwmon->update_lock); + + return mcu_data; +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int channel = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", nsa320_input_names[channel]); +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + s32 mcu_data = nsa320_hwmon_update(dev); + + if (mcu_data < 0) + return mcu_data; + + return sprintf(buf, "%d\n", (mcu_data & 0xffff) * 100); +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + s32 mcu_data = nsa320_hwmon_update(dev); + + if (mcu_data < 0) + return mcu_data; + + return sprintf(buf, "%d\n", ((mcu_data & 0xff0000) >> 16) * 100); +} + +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, NSA320_TEMP); +static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL); +static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, show_label, NULL, NSA320_FAN); +static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL); + +static struct attribute *nsa320_attrs[] = { + &sensor_dev_attr_temp1_label.dev_attr.attr, + &dev_attr_temp1_input.attr, + &sensor_dev_attr_fan1_label.dev_attr.attr, + &dev_attr_fan1_input.attr, + NULL +}; + +ATTRIBUTE_GROUPS(nsa320); + +static const struct of_device_id of_nsa320_hwmon_match[] = { + { .compatible = "zyxel,nsa320-mcu", }, + { }, +}; + +static int nsa320_hwmon_probe(struct platform_device *pdev) +{ + struct nsa320_hwmon *hwmon; + struct device *classdev; + + hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + /* Look up the GPIO pins to use */ + hwmon->act = devm_gpiod_get(&pdev->dev, "act", GPIOD_OUT_LOW); + if (IS_ERR(hwmon->act)) + return PTR_ERR(hwmon->act); + + hwmon->clk = devm_gpiod_get(&pdev->dev, "clk", GPIOD_OUT_HIGH); + if (IS_ERR(hwmon->clk)) + return PTR_ERR(hwmon->clk); + + hwmon->data = devm_gpiod_get(&pdev->dev, "data", GPIOD_IN); + if (IS_ERR(hwmon->data)) + return PTR_ERR(hwmon->data); + + mutex_init(&hwmon->update_lock); + + classdev = devm_hwmon_device_register_with_groups(&pdev->dev, + "nsa320", hwmon, nsa320_groups); + + return PTR_ERR_OR_ZERO(classdev); + +} + +/* All allocations use devres so remove() is not needed. */ + +static struct platform_driver nsa320_hwmon_driver = { + .probe = nsa320_hwmon_probe, + .driver = { + .name = "nsa320-hwmon", + .of_match_table = of_match_ptr(of_nsa320_hwmon_match), + }, +}; + +module_platform_driver(nsa320_hwmon_driver); + +MODULE_DEVICE_TABLE(of, of_nsa320_hwmon_match); +MODULE_AUTHOR("Peter Schildmann "); +MODULE_AUTHOR("Adam Baker "); +MODULE_DESCRIPTION("NSA320 Hardware Monitoring"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:nsa320-hwmon"); -- cgit v0.10.2