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

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

 



Thanks for your review! Replies inline.

Tiberiu

> -----Original Message-----
> From: Guenter Roeck [mailto:linux@xxxxxxxxxxxx]
> Sent: Tuesday, March 22, 2016 4:57 PM
> To: Breana, Tiberiu A <tiberiu.a.breana@xxxxxxxxx>; lm-sensors@lm-
> sensors.org
> Cc: Baluta, Daniel <daniel.baluta@xxxxxxxxx>
> Subject: Re: [PATCH 2/2] hwmon: (max31722) Add alarm support
> 
> 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 ?

My bad, this is some leftover code from a development version that
uses gpio to receive interrupts.

> 
> > +#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.

This is the chip's operating range. The datasheet states that temperatures
outside this range will likely cause permanent damage to the device. I have
no means of testing the device's operation outside of those conditions, so
I suggest we enforce the limits specified from the datasheet.

> 
> >   #define MAX31722_REGMAP_NAME
> 	"max31722_regmap"
> > +#define MAX31722_GPIO
> 	"max31722_gpio"
> >
> Where is this used ?

Again, leftover code. Will delete this.

> 
> >   #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.

Agreed, will drop this.

> 
> > +
> >   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.

Done.

> 
> >   {
> >   	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.

Will use clamp_val() in v2.

> 
> > +	/*
> > +	* 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).

Will use this in v2.

> 
> > +	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.

That does indeed sound more logical. I'll change the attributes in v2.

> 
> > +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.

I was somewhat conflicted as well when implementing interrupt support for the
chip via the hwmon alarm attribute. In IIO it was simpler; when an interrupt is
received, we send an event to userspace that a threshold has been met.
We can't check a register to see which threshold was met, so we sent an
"either direction" event and let the user check.

The hwmon sysfs-interface doc explicitly says "drivers do NOT make
comparisons of readings to thresholds". Are you suggesting otherwise?

> 
> > +
> > +	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.

In the default comparator mode, if a threshold was surpassed, TOUT will be
active and it should trigger the interrupt handler, setting alarm_active to true.

> 
> >   	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 ?

devm_request_threaded_irq won't allow the request without a "top half"
irq handler unless the oneshot flag is added. From what I've seen working
with the device, there's no need to manually reactivate interrupts.

> 
> > +						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.
>

That makes sense. I'll implement this in v2.

> 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.

There is no assumption that the chip starts in interrupt mode. As mentioned
in the cover letter, the driver was written for comparator mode.

> 
> > +
> >   	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