Commit 9547d6a4 authored by Dimitri Fedrau's avatar Dimitri Fedrau Committed by Jonathan Cameron

iio: humidity: hdc3020: fix hysteresis representation

According to the ABI docs hysteresis values are represented as offsets to
threshold values. Current implementation represents hysteresis values as
absolute values which is wrong. Nevertheless the device stores them as
absolute values and the datasheet refers to them as clear thresholds. Fix
the reading and writing of hysteresis values by including thresholds into
calculations. Hysteresis values that result in threshold clear values
that are out of limits will be truncated.

To check that the threshold clear values are correct, registers are read
out using i2ctransfer and the corresponding temperature and relative
humidity thresholds are calculated using the formulas in the datasheet.

Fixes: 3ad0e7e5 ("iio: humidity: hdc3020: add threshold events support")
Signed-off-by: default avatarDimitri Fedrau <dima.fedrau@gmail.com>
Reviewed-by: default avatarJavier Carrasco <javier.carrasco.cruz@gmail.com>
Link: https://lore.kernel.org/r/20240605192136.38146-1-dima.fedrau@gmail.com
Cc: <Stable@vger.kernel.org>
Signed-off-by: default avatarJonathan Cameron <Jonathan.Cameron@huawei.com>
parent 75183e46
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/math64.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/pm.h> #include <linux/pm.h>
...@@ -66,8 +67,10 @@ ...@@ -66,8 +67,10 @@
#define HDC3020_CRC8_POLYNOMIAL 0x31 #define HDC3020_CRC8_POLYNOMIAL 0x31
#define HDC3020_MIN_TEMP -40 #define HDC3020_MIN_TEMP_MICRO -39872968
#define HDC3020_MAX_TEMP 125 #define HDC3020_MAX_TEMP_MICRO 124875639
#define HDC3020_MAX_TEMP_HYST_MICRO 164748607
#define HDC3020_MAX_HUM_MICRO 99220264
struct hdc3020_data { struct hdc3020_data {
struct i2c_client *client; struct i2c_client *client;
...@@ -368,6 +371,105 @@ static int hdc3020_write_raw(struct iio_dev *indio_dev, ...@@ -368,6 +371,105 @@ static int hdc3020_write_raw(struct iio_dev *indio_dev,
return -EINVAL; return -EINVAL;
} }
static int hdc3020_thresh_get_temp(u16 thresh)
{
int temp;
/*
* Get the temperature threshold from 9 LSBs, shift them to get
* the truncated temperature threshold representation and
* calculate the threshold according to the formula in the
* datasheet. Result is degree celsius scaled by 65535.
*/
temp = FIELD_GET(HDC3020_THRESH_TEMP_MASK, thresh) <<
HDC3020_THRESH_TEMP_TRUNC_SHIFT;
return -2949075 + (175 * temp);
}
static int hdc3020_thresh_get_hum(u16 thresh)
{
int hum;
/*
* Get the humidity threshold from 7 MSBs, shift them to get the
* truncated humidity threshold representation and calculate the
* threshold according to the formula in the datasheet. Result is
* percent scaled by 65535.
*/
hum = FIELD_GET(HDC3020_THRESH_HUM_MASK, thresh) <<
HDC3020_THRESH_HUM_TRUNC_SHIFT;
return hum * 100;
}
static u16 hdc3020_thresh_set_temp(int s_temp, u16 curr_thresh)
{
u64 temp;
u16 thresh;
/*
* Calculate temperature threshold, shift it down to get the
* truncated threshold representation in the 9LSBs while keeping
* the current humidity threshold in the 7 MSBs.
*/
temp = (u64)(s_temp + 45000000) * 65535ULL;
temp = div_u64(temp, 1000000 * 175) >> HDC3020_THRESH_TEMP_TRUNC_SHIFT;
thresh = FIELD_PREP(HDC3020_THRESH_TEMP_MASK, temp);
thresh |= (FIELD_GET(HDC3020_THRESH_HUM_MASK, curr_thresh) <<
HDC3020_THRESH_HUM_TRUNC_SHIFT);
return thresh;
}
static u16 hdc3020_thresh_set_hum(int s_hum, u16 curr_thresh)
{
u64 hum;
u16 thresh;
/*
* Calculate humidity threshold, shift it down and up to get the
* truncated threshold representation in the 7MSBs while keeping
* the current temperature threshold in the 9 LSBs.
*/
hum = (u64)(s_hum) * 65535ULL;
hum = div_u64(hum, 1000000 * 100) >> HDC3020_THRESH_HUM_TRUNC_SHIFT;
thresh = FIELD_PREP(HDC3020_THRESH_HUM_MASK, hum);
thresh |= FIELD_GET(HDC3020_THRESH_TEMP_MASK, curr_thresh);
return thresh;
}
static
int hdc3020_thresh_clr(s64 s_thresh, s64 s_hyst, enum iio_event_direction dir)
{
s64 s_clr;
/*
* Include directions when calculation the clear value,
* since hysteresis is unsigned by definition and the
* clear value is an absolute value which is signed.
*/
if (dir == IIO_EV_DIR_RISING)
s_clr = s_thresh - s_hyst;
else
s_clr = s_thresh + s_hyst;
/* Divide by 65535 to get units of micro */
return div_s64(s_clr, 65535);
}
static int _hdc3020_write_thresh(struct hdc3020_data *data, u16 reg, u16 val)
{
u8 buf[5];
put_unaligned_be16(reg, buf);
put_unaligned_be16(val, buf + 2);
buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
return hdc3020_write_bytes(data, buf, 5);
}
static int hdc3020_write_thresh(struct iio_dev *indio_dev, static int hdc3020_write_thresh(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan, const struct iio_chan_spec *chan,
enum iio_event_type type, enum iio_event_type type,
...@@ -376,67 +478,126 @@ static int hdc3020_write_thresh(struct iio_dev *indio_dev, ...@@ -376,67 +478,126 @@ static int hdc3020_write_thresh(struct iio_dev *indio_dev,
int val, int val2) int val, int val2)
{ {
struct hdc3020_data *data = iio_priv(indio_dev); struct hdc3020_data *data = iio_priv(indio_dev);
u8 buf[5]; u16 reg, reg_val, reg_thresh_rd, reg_clr_rd, reg_thresh_wr, reg_clr_wr;
u64 tmp; s64 s_thresh, s_hyst, s_clr;
u16 reg; int s_val, thresh, clr, ret;
int ret;
/* Select threshold registers */
/* Supported temperature range is from –40 to 125 degree celsius */ if (dir == IIO_EV_DIR_RISING) {
if (val < HDC3020_MIN_TEMP || val > HDC3020_MAX_TEMP) reg_thresh_rd = HDC3020_R_T_RH_THRESH_HIGH;
return -EINVAL; reg_thresh_wr = HDC3020_S_T_RH_THRESH_HIGH;
reg_clr_rd = HDC3020_R_T_RH_THRESH_HIGH_CLR;
/* Select threshold register */ reg_clr_wr = HDC3020_S_T_RH_THRESH_HIGH_CLR;
if (info == IIO_EV_INFO_VALUE) {
if (dir == IIO_EV_DIR_RISING)
reg = HDC3020_S_T_RH_THRESH_HIGH;
else
reg = HDC3020_S_T_RH_THRESH_LOW;
} else { } else {
if (dir == IIO_EV_DIR_RISING) reg_thresh_rd = HDC3020_R_T_RH_THRESH_LOW;
reg = HDC3020_S_T_RH_THRESH_HIGH_CLR; reg_thresh_wr = HDC3020_S_T_RH_THRESH_LOW;
else reg_clr_rd = HDC3020_R_T_RH_THRESH_LOW_CLR;
reg = HDC3020_S_T_RH_THRESH_LOW_CLR; reg_clr_wr = HDC3020_S_T_RH_THRESH_LOW_CLR;
} }
guard(mutex)(&data->lock); guard(mutex)(&data->lock);
ret = hdc3020_read_be16(data, reg); ret = hdc3020_read_be16(data, reg_thresh_rd);
if (ret < 0)
return ret;
thresh = ret;
ret = hdc3020_read_be16(data, reg_clr_rd);
if (ret < 0) if (ret < 0)
return ret; return ret;
clr = ret;
/* Scale value to include decimal part into calculations */
s_val = (val < 0) ? (val * 1000000 - val2) : (val * 1000000 + val2);
switch (chan->type) { switch (chan->type) {
case IIO_TEMP: case IIO_TEMP:
/* switch (info) {
* Calculate temperature threshold, shift it down to get the case IIO_EV_INFO_VALUE:
* truncated threshold representation in the 9LSBs while keeping s_val = max(s_val, HDC3020_MIN_TEMP_MICRO);
* the current humidity threshold in the 7 MSBs. s_val = min(s_val, HDC3020_MAX_TEMP_MICRO);
*/ reg = reg_thresh_wr;
tmp = ((u64)(((val + 45) * MICRO) + val2)) * 65535ULL; reg_val = hdc3020_thresh_set_temp(s_val, thresh);
tmp = div_u64(tmp, MICRO * 175); ret = _hdc3020_write_thresh(data, reg, reg_val);
val = tmp >> HDC3020_THRESH_TEMP_TRUNC_SHIFT; if (ret < 0)
val = FIELD_PREP(HDC3020_THRESH_TEMP_MASK, val); return ret;
val |= (FIELD_GET(HDC3020_THRESH_HUM_MASK, ret) <<
HDC3020_THRESH_HUM_TRUNC_SHIFT); /* Calculate old hysteresis */
s_thresh = (s64)hdc3020_thresh_get_temp(thresh) * 1000000;
s_clr = (s64)hdc3020_thresh_get_temp(clr) * 1000000;
s_hyst = div_s64(abs(s_thresh - s_clr), 65535);
/* Set new threshold */
thresh = reg_val;
/* Set old hysteresis */
s_val = s_hyst;
fallthrough;
case IIO_EV_INFO_HYSTERESIS:
/*
* Function hdc3020_thresh_get_temp returns temperature
* in degree celsius scaled by 65535. Scale by 1000000
* to be able to subtract scaled hysteresis value.
*/
s_thresh = (s64)hdc3020_thresh_get_temp(thresh) * 1000000;
/*
* Units of s_val are in micro degree celsius, scale by
* 65535 to get same units as s_thresh.
*/
s_val = min(abs(s_val), HDC3020_MAX_TEMP_HYST_MICRO);
s_hyst = (s64)s_val * 65535;
s_clr = hdc3020_thresh_clr(s_thresh, s_hyst, dir);
s_clr = max(s_clr, HDC3020_MIN_TEMP_MICRO);
s_clr = min(s_clr, HDC3020_MAX_TEMP_MICRO);
reg = reg_clr_wr;
reg_val = hdc3020_thresh_set_temp(s_clr, clr);
break;
default:
return -EOPNOTSUPP;
}
break; break;
case IIO_HUMIDITYRELATIVE: case IIO_HUMIDITYRELATIVE:
/* s_val = (s_val < 0) ? 0 : min(s_val, HDC3020_MAX_HUM_MICRO);
* Calculate humidity threshold, shift it down and up to get the switch (info) {
* truncated threshold representation in the 7MSBs while keeping case IIO_EV_INFO_VALUE:
* the current temperature threshold in the 9 LSBs. reg = reg_thresh_wr;
*/ reg_val = hdc3020_thresh_set_hum(s_val, thresh);
tmp = ((u64)((val * MICRO) + val2)) * 65535ULL; ret = _hdc3020_write_thresh(data, reg, reg_val);
tmp = div_u64(tmp, MICRO * 100); if (ret < 0)
val = tmp >> HDC3020_THRESH_HUM_TRUNC_SHIFT; return ret;
val = FIELD_PREP(HDC3020_THRESH_HUM_MASK, val);
val |= FIELD_GET(HDC3020_THRESH_TEMP_MASK, ret); /* Calculate old hysteresis */
s_thresh = (s64)hdc3020_thresh_get_hum(thresh) * 1000000;
s_clr = (s64)hdc3020_thresh_get_hum(clr) * 1000000;
s_hyst = div_s64(abs(s_thresh - s_clr), 65535);
/* Set new threshold */
thresh = reg_val;
/* Try to set old hysteresis */
s_val = min(abs(s_hyst), HDC3020_MAX_HUM_MICRO);
fallthrough;
case IIO_EV_INFO_HYSTERESIS:
/*
* Function hdc3020_thresh_get_hum returns relative
* humidity in percent scaled by 65535. Scale by 1000000
* to be able to subtract scaled hysteresis value.
*/
s_thresh = (s64)hdc3020_thresh_get_hum(thresh) * 1000000;
/*
* Units of s_val are in micro percent, scale by 65535
* to get same units as s_thresh.
*/
s_hyst = (s64)s_val * 65535;
s_clr = hdc3020_thresh_clr(s_thresh, s_hyst, dir);
s_clr = max(s_clr, 0);
s_clr = min(s_clr, HDC3020_MAX_HUM_MICRO);
reg = reg_clr_wr;
reg_val = hdc3020_thresh_set_hum(s_clr, clr);
break;
default:
return -EOPNOTSUPP;
}
break; break;
default: default:
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
put_unaligned_be16(reg, buf); return _hdc3020_write_thresh(data, reg, reg_val);
put_unaligned_be16(val, buf + 2);
buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
return hdc3020_write_bytes(data, buf, 5);
} }
static int hdc3020_read_thresh(struct iio_dev *indio_dev, static int hdc3020_read_thresh(struct iio_dev *indio_dev,
...@@ -447,48 +608,60 @@ static int hdc3020_read_thresh(struct iio_dev *indio_dev, ...@@ -447,48 +608,60 @@ static int hdc3020_read_thresh(struct iio_dev *indio_dev,
int *val, int *val2) int *val, int *val2)
{ {
struct hdc3020_data *data = iio_priv(indio_dev); struct hdc3020_data *data = iio_priv(indio_dev);
u16 reg; u16 reg_thresh, reg_clr;
int ret; int thresh, clr, ret;
/* Select threshold register */ /* Select threshold registers */
if (info == IIO_EV_INFO_VALUE) { if (dir == IIO_EV_DIR_RISING) {
if (dir == IIO_EV_DIR_RISING) reg_thresh = HDC3020_R_T_RH_THRESH_HIGH;
reg = HDC3020_R_T_RH_THRESH_HIGH; reg_clr = HDC3020_R_T_RH_THRESH_HIGH_CLR;
else
reg = HDC3020_R_T_RH_THRESH_LOW;
} else { } else {
if (dir == IIO_EV_DIR_RISING) reg_thresh = HDC3020_R_T_RH_THRESH_LOW;
reg = HDC3020_R_T_RH_THRESH_HIGH_CLR; reg_clr = HDC3020_R_T_RH_THRESH_LOW_CLR;
else
reg = HDC3020_R_T_RH_THRESH_LOW_CLR;
} }
guard(mutex)(&data->lock); guard(mutex)(&data->lock);
ret = hdc3020_read_be16(data, reg); ret = hdc3020_read_be16(data, reg_thresh);
if (ret < 0) if (ret < 0)
return ret; return ret;
switch (chan->type) { switch (chan->type) {
case IIO_TEMP: case IIO_TEMP:
/* thresh = hdc3020_thresh_get_temp(ret);
* Get the temperature threshold from 9 LSBs, shift them to get switch (info) {
* the truncated temperature threshold representation and case IIO_EV_INFO_VALUE:
* calculate the threshold according to the formula in the *val = thresh;
* datasheet. break;
*/ case IIO_EV_INFO_HYSTERESIS:
*val = FIELD_GET(HDC3020_THRESH_TEMP_MASK, ret); ret = hdc3020_read_be16(data, reg_clr);
*val = *val << HDC3020_THRESH_TEMP_TRUNC_SHIFT; if (ret < 0)
*val = -2949075 + (175 * (*val)); return ret;
clr = hdc3020_thresh_get_temp(ret);
*val = abs(thresh - clr);
break;
default:
return -EOPNOTSUPP;
}
*val2 = 65535; *val2 = 65535;
return IIO_VAL_FRACTIONAL; return IIO_VAL_FRACTIONAL;
case IIO_HUMIDITYRELATIVE: case IIO_HUMIDITYRELATIVE:
/* thresh = hdc3020_thresh_get_hum(ret);
* Get the humidity threshold from 7 MSBs, shift them to get the switch (info) {
* truncated humidity threshold representation and calculate the case IIO_EV_INFO_VALUE:
* threshold according to the formula in the datasheet. *val = thresh;
*/ break;
*val = FIELD_GET(HDC3020_THRESH_HUM_MASK, ret); case IIO_EV_INFO_HYSTERESIS:
*val = (*val << HDC3020_THRESH_HUM_TRUNC_SHIFT) * 100; ret = hdc3020_read_be16(data, reg_clr);
if (ret < 0)
return ret;
clr = hdc3020_thresh_get_hum(ret);
*val = abs(thresh - clr);
break;
default:
return -EOPNOTSUPP;
}
*val2 = 65535; *val2 = 65535;
return IIO_VAL_FRACTIONAL; return IIO_VAL_FRACTIONAL;
default: default:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment