Re: [PATCH 2/2] hwmon: (max31722) Add alarm support

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

 



On 03/22/2016 04:41 AM, Tiberiu Breana wrote:
Add temperature threshold alarm support for the max31722
sensor driver.

Signed-off-by: Tiberiu Breana <tiberiu.a.breana@xxxxxxxxx>
---
  Documentation/hwmon/max31722 |   7 +++
  drivers/hwmon/max31722.c     | 130 +++++++++++++++++++++++++++++++++++++++++--
  2 files changed, 131 insertions(+), 6 deletions(-)

diff --git a/Documentation/hwmon/max31722 b/Documentation/hwmon/max31722
index 090da845..e247963 100644
--- a/Documentation/hwmon/max31722
+++ b/Documentation/hwmon/max31722
@@ -25,6 +25,10 @@ Usage Notes
  -----------

  This driver uses ACPI to auto-detect devices. See ACPI IDs in the above section.
+The sensor supports a temperature alarm. This is set once the measured
+temperature goes above a user-set threshold (temp1_max) and will be cleared
+once the temperature goes below temp1_min. See the datasheet, page 9,
+"Comparator Mode" for details.

  Sysfs entries
  -------------
@@ -32,3 +36,6 @@ Sysfs entries
  The following attribute is supported:

  temp1_input		Measured temperature. Read-only.
+temp1_alarm		Temperature alarm. Read-only.
+temp1_min		Minimum temperature threshold. Read-write.
+temp1_max		Maximum temperature threshold. Read-write.
diff --git a/drivers/hwmon/max31722.c b/drivers/hwmon/max31722.c
index 13ba906..8e14eed 100644
--- a/drivers/hwmon/max31722.c
+++ b/drivers/hwmon/max31722.c
@@ -11,6 +11,8 @@

  #include <linux/kernel.h>
  #include <linux/acpi.h>
+#include <linux/gpio/consumer.h>

Why is this needed ?

+#include <linux/interrupt.h>
  #include <linux/module.h>
  #include <linux/regmap.h>
  #include <linux/spi/spi.h>
@@ -20,13 +22,20 @@
  #define MAX31722_REG_CFG				0x00
  #define MAX31722_REG_TEMP_LSB				0x01
  #define MAX31722_REG_TEMP_MSB				0x02
+#define MAX31722_REG_THIGH_LSB				0x03
+#define MAX31722_REG_TLOW_LSB				0x05
  #define MAX31722_MAX_REG				0x86

  #define MAX31722_MODE_CONTINUOUS			0x00
  #define MAX31722_MODE_STANDBY				0x01
  #define MAX31722_RESOLUTION_11BIT			0x02

+/* Minimum and maximum supported temperatures, in millidegrees */
+#define MAX31722_MIN_TEMP				-55000
+#define MAX31722_MAX_TEMP				125000
+

Question here is if those are chip operating ranges or register limits.
Reason for asking is that it might be (and likely is) possible to write
values outside this range into the chip. If so, those are the limits
you should use. Otherwise we could end up in situations where the
chip reports a lower limit of -100 degrees C, but it would be impossible
to write that value back into the chip.

  #define MAX31722_REGMAP_NAME				"max31722_regmap"
+#define MAX31722_GPIO					"max31722_gpio"

Where is this used ?

  #define MAX31722_REGFIELD(name)						    \
  	do {								    \
@@ -39,12 +48,27 @@
  		}							    \
  	} while (0)

+enum attr_index {
+	t_input,
+	t_min,
+	t_max,
+	t_alarm,
+	t_num_regs
+};
+
+static const u8 max31722_regs[t_num_regs] = {
+	[t_input]	= MAX31722_REG_TEMP_LSB,
+	[t_min]		= MAX31722_REG_TLOW_LSB,
+	[t_max]		= MAX31722_REG_THIGH_LSB,
+};

The only use of this array, as far as I can see, is to convert
t_{input, min, max} into register addresses. You can write the
register directly into attr->index and drop this indirection.

+
  struct max31722_data {
  	struct spi_device *spi_device;
  	struct device *hwmon_dev;
  	struct regmap *regmap;
  	struct regmap_field *reg_state;
  	struct regmap_field *reg_resolution;
+	bool alarm_active;
  };

  /*
@@ -117,9 +141,9 @@ static ssize_t max31722_show_name(struct device *dev,
  	return sprintf(buf, "%s\n", to_spi_device(dev)->modalias);
  }

-static ssize_t max31722_show_temperature(struct device *dev,
-					 struct device_attribute *attr,
-					 char *buf)
+static ssize_t max31722_show_temp(struct device *dev,
+				  struct device_attribute *devattr,
+				  char *buf)

If you are going to use max31722_show_temp(), might as well introduce it with
that name in the first patch.

  {
  	int i;
  	int ret;
@@ -127,8 +151,10 @@ static ssize_t max31722_show_temperature(struct device *dev,
  	s16 val;
  	u16 temp;
  	struct max31722_data *data = dev_get_drvdata(dev);
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);

-	ret = regmap_bulk_read(data->regmap, MAX31722_REG_TEMP_LSB, &temp, 2);
+	ret = regmap_bulk_read(data->regmap,
+			       max31722_regs[attr->index], &temp, 2);
  	if (ret < 0) {
  		dev_err(&data->spi_device->dev,
  			"failed to read temperature register\n");
@@ -152,13 +178,79 @@ static ssize_t max31722_show_temperature(struct device *dev,
  	return sprintf(buf, "%d\n", val);
  }

+static ssize_t max31722_set_temp(struct device *dev,
+				 struct device_attribute *devattr,
+				 const char *buf, size_t count)
+{
+	int i;
+	int ret;
+	int fract;
+	u16 thresh;
+	u8 lsb;
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	long val;
+	struct max31722_data *data = dev_get_drvdata(dev);
+
+	ret = kstrtol(buf, 10, &val);
+	if (ret < 0)
+		return ret;
+
+	if (val < MAX31722_MIN_TEMP || val > MAX31722_MAX_TEMP)
+		return -EINVAL;

Please use clamp_val(). We don't expect users to know chip limits.

+	/*
+	* Convert input to a register value. First round down the value to one
+	* that can be represented in the 11 bit resolution.
+	*/
+	val -= val % max31722_milli_table[2];
+
+	fract = val % 1000;
+
+	lsb = 0;
+	for (i = 0 ; i < ARRAY_SIZE(max31722_milli_table) && fract > 0; i++)
+	if (fract - max31722_milli_table[i] >= 0) {
+		fract -= max31722_milli_table[i];
+		lsb += 1 << (3 - i - 1);
+	}
+	lsb <<= 5;
+
+	thresh = (val / 1000) << 8 | lsb;

	thresh = cpu_to_le16(DIV_ROUND_CLOSEST(val, 125) << 5);

should accomplish the same (and also work on big endian systems).

+	ret = regmap_bulk_write(data->regmap,
+				max31722_regs[attr->index], &thresh, 2);
+	if (ret < 0) {
+		dev_err(&data->spi_device->dev,
+			"failed to write threshold register\n");

Please no log message here.

+		return ret;
+	}
+
+	return count;
+}
+
+static ssize_t max31722_show_alarm(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct spi_device *spi_device = to_spi_device(dev);
+	struct max31722_data *data = spi_get_drvdata(spi_device);
+
+	return sprintf(buf, "%d\n", data->alarm_active ? 1 : 0);

bool auto-converts to 0/1, so you can just print data->alarm_active.

+}
+
  static DEVICE_ATTR(name, S_IRUGO, max31722_show_name, NULL);
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
-			  max31722_show_temperature, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, max31722_show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, max31722_show_temp,
+			  max31722_set_temp, t_min);
+static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, max31722_show_temp,
+			  max31722_set_temp, t_max);

From the datasheet, this is not really min/max, but max_hyst/max.

+static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, max31722_show_alarm, NULL,
+			  t_alarm);

There is no alarm register, so you might as well just use DEVICE_ATTR here.

+

  static struct attribute *max31722_attributes[] = {
  	&dev_attr_name.attr,
  	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_alarm.dev_attr.attr,

Attributes should be max_hyst, max, and max_alarm.

  	NULL,
  };

@@ -166,6 +258,18 @@ static const struct attribute_group max31722_group = {
  	.attrs = max31722_attributes,
  };

+static irqreturn_t max31722_irq_handler(int irq, void *private)
+{
+	struct max31722_data *data = private;
+	/*
+	 * The device will issue cyclical interrupts when the
+	 * THIGH/TLOW thresholds are met.
+	 */
+	data->alarm_active = !data->alarm_active;

'Cyclical' is a bit misleading. Yes, the term is used in the data sheet, but
what it really means is that the alarm is activated if the temperature exceeds
Thigh and deactivated if the temperature goes below Tlow.

However, I am more than a bit concerned about this code, It assumes that
there is no alarm when the driver is instantiated, and it assumes that
interrupts don't get lost.

I think your real only option here is to explicitly read the current temperature
and compare it to Thigh/Tlow to see if there is an alarm. This makes this
function a bit tricky; it would have to be something like

	if (temperature > Thigh)
		alarm_active = true;
	else if (temperature < Tlow)
		alarm_active = false;

with a grey area what to do if the measured temperature is between Tlow and Thigh.
Maybe flip alarm_active in that case ?

You might also consider generating a sysfs and/or udev notification on the
alarm attribute here.

+
+	return IRQ_HANDLED;
+}
+
  static int max31722_init(struct max31722_data *data)
  {
  	int ret = 0;
@@ -196,6 +300,8 @@ static int max31722_init(struct max31722_data *data)
  	if (ret < 0)
  		goto err;

+	data->alarm_active = false;
+

That assumes that there is currently no alarm, which we don't know.
Best solution might be to have a function update_alarm_status() and
call it from here as well as from the interrupt handler.

  	return 0;

  err:
@@ -223,6 +329,18 @@ static int max31722_probe(struct spi_device *spi)
  	if (ret < 0)
  		goto err_standby;

+	if (spi->irq > 0) {
+		ret = devm_request_threaded_irq(&spi->dev, spi->irq,
+						NULL,
+						max31722_irq_handler,
+						IRQF_TRIGGER_LOW | IRQF_ONESHOT,

Doesn't IRQF_ONESHOT mean you would have to re-enable the interrupt ?

+						dev_name(&spi->dev), data);
+		if (ret < 0) {
+			dev_err(&spi->dev, "request irq %d failed\n", spi->irq);
+			goto err_remove_group;
+		}
+	}

This means that the alarm is only supported if there is interrupt support.
The alarm attribute should therefore only exist if interrupts are supported.
The easiest way to implement this would be by creating a separate sensor group
for the alarm attribute (though you would have to add the list of groups to
max31722_data). Another option would be to use .is_visible.

Another problem is initialization: Your code assumes that the chip is in
interrupt mode. What if the BIOS/ROMMON programmed it to comparator mode ?
In that case you would have to either reprogram it, or change the interrupt
to edge triggered (I think).

The datasheet suggests that the chip is initially in comparator mode,
which makes me wonder if/how you tested this code.

+
  	data->hwmon_dev = hwmon_device_register(&spi->dev);
  	if (IS_ERR(data->hwmon_dev)) {
  		ret = PTR_ERR(data->hwmon_dev);



_______________________________________________
lm-sensors mailing list
lm-sensors@xxxxxxxxxxxxxx
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors



[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux