Skip to content
This repository was archived by the owner on Nov 8, 2023. It is now read-only.

Commit 9547d6a

Browse files
Dimitri Fedraujic23
authored andcommitted
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: 3ad0e7e ("iio: humidity: hdc3020: add threshold events support") Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com> Reviewed-by: Javier 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: Jonathan Cameron <Jonathan.Cameron@huawei.com>
1 parent 75183e4 commit 9547d6a

File tree

1 file changed

+249
-76
lines changed

1 file changed

+249
-76
lines changed

drivers/iio/humidity/hdc3020.c

Lines changed: 249 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <linux/i2c.h>
2020
#include <linux/init.h>
2121
#include <linux/interrupt.h>
22+
#include <linux/math64.h>
2223
#include <linux/module.h>
2324
#include <linux/mutex.h>
2425
#include <linux/pm.h>
@@ -66,8 +67,10 @@
6667

6768
#define HDC3020_CRC8_POLYNOMIAL 0x31
6869

69-
#define HDC3020_MIN_TEMP -40
70-
#define HDC3020_MAX_TEMP 125
70+
#define HDC3020_MIN_TEMP_MICRO -39872968
71+
#define HDC3020_MAX_TEMP_MICRO 124875639
72+
#define HDC3020_MAX_TEMP_HYST_MICRO 164748607
73+
#define HDC3020_MAX_HUM_MICRO 99220264
7174

7275
struct hdc3020_data {
7376
struct i2c_client *client;
@@ -368,6 +371,105 @@ static int hdc3020_write_raw(struct iio_dev *indio_dev,
368371
return -EINVAL;
369372
}
370373

374+
static int hdc3020_thresh_get_temp(u16 thresh)
375+
{
376+
int temp;
377+
378+
/*
379+
* Get the temperature threshold from 9 LSBs, shift them to get
380+
* the truncated temperature threshold representation and
381+
* calculate the threshold according to the formula in the
382+
* datasheet. Result is degree celsius scaled by 65535.
383+
*/
384+
temp = FIELD_GET(HDC3020_THRESH_TEMP_MASK, thresh) <<
385+
HDC3020_THRESH_TEMP_TRUNC_SHIFT;
386+
387+
return -2949075 + (175 * temp);
388+
}
389+
390+
static int hdc3020_thresh_get_hum(u16 thresh)
391+
{
392+
int hum;
393+
394+
/*
395+
* Get the humidity threshold from 7 MSBs, shift them to get the
396+
* truncated humidity threshold representation and calculate the
397+
* threshold according to the formula in the datasheet. Result is
398+
* percent scaled by 65535.
399+
*/
400+
hum = FIELD_GET(HDC3020_THRESH_HUM_MASK, thresh) <<
401+
HDC3020_THRESH_HUM_TRUNC_SHIFT;
402+
403+
return hum * 100;
404+
}
405+
406+
static u16 hdc3020_thresh_set_temp(int s_temp, u16 curr_thresh)
407+
{
408+
u64 temp;
409+
u16 thresh;
410+
411+
/*
412+
* Calculate temperature threshold, shift it down to get the
413+
* truncated threshold representation in the 9LSBs while keeping
414+
* the current humidity threshold in the 7 MSBs.
415+
*/
416+
temp = (u64)(s_temp + 45000000) * 65535ULL;
417+
temp = div_u64(temp, 1000000 * 175) >> HDC3020_THRESH_TEMP_TRUNC_SHIFT;
418+
thresh = FIELD_PREP(HDC3020_THRESH_TEMP_MASK, temp);
419+
thresh |= (FIELD_GET(HDC3020_THRESH_HUM_MASK, curr_thresh) <<
420+
HDC3020_THRESH_HUM_TRUNC_SHIFT);
421+
422+
return thresh;
423+
}
424+
425+
static u16 hdc3020_thresh_set_hum(int s_hum, u16 curr_thresh)
426+
{
427+
u64 hum;
428+
u16 thresh;
429+
430+
/*
431+
* Calculate humidity threshold, shift it down and up to get the
432+
* truncated threshold representation in the 7MSBs while keeping
433+
* the current temperature threshold in the 9 LSBs.
434+
*/
435+
hum = (u64)(s_hum) * 65535ULL;
436+
hum = div_u64(hum, 1000000 * 100) >> HDC3020_THRESH_HUM_TRUNC_SHIFT;
437+
thresh = FIELD_PREP(HDC3020_THRESH_HUM_MASK, hum);
438+
thresh |= FIELD_GET(HDC3020_THRESH_TEMP_MASK, curr_thresh);
439+
440+
return thresh;
441+
}
442+
443+
static
444+
int hdc3020_thresh_clr(s64 s_thresh, s64 s_hyst, enum iio_event_direction dir)
445+
{
446+
s64 s_clr;
447+
448+
/*
449+
* Include directions when calculation the clear value,
450+
* since hysteresis is unsigned by definition and the
451+
* clear value is an absolute value which is signed.
452+
*/
453+
if (dir == IIO_EV_DIR_RISING)
454+
s_clr = s_thresh - s_hyst;
455+
else
456+
s_clr = s_thresh + s_hyst;
457+
458+
/* Divide by 65535 to get units of micro */
459+
return div_s64(s_clr, 65535);
460+
}
461+
462+
static int _hdc3020_write_thresh(struct hdc3020_data *data, u16 reg, u16 val)
463+
{
464+
u8 buf[5];
465+
466+
put_unaligned_be16(reg, buf);
467+
put_unaligned_be16(val, buf + 2);
468+
buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
469+
470+
return hdc3020_write_bytes(data, buf, 5);
471+
}
472+
371473
static int hdc3020_write_thresh(struct iio_dev *indio_dev,
372474
const struct iio_chan_spec *chan,
373475
enum iio_event_type type,
@@ -376,67 +478,126 @@ static int hdc3020_write_thresh(struct iio_dev *indio_dev,
376478
int val, int val2)
377479
{
378480
struct hdc3020_data *data = iio_priv(indio_dev);
379-
u8 buf[5];
380-
u64 tmp;
381-
u16 reg;
382-
int ret;
383-
384-
/* Supported temperature range is from –40 to 125 degree celsius */
385-
if (val < HDC3020_MIN_TEMP || val > HDC3020_MAX_TEMP)
386-
return -EINVAL;
387-
388-
/* Select threshold register */
389-
if (info == IIO_EV_INFO_VALUE) {
390-
if (dir == IIO_EV_DIR_RISING)
391-
reg = HDC3020_S_T_RH_THRESH_HIGH;
392-
else
393-
reg = HDC3020_S_T_RH_THRESH_LOW;
481+
u16 reg, reg_val, reg_thresh_rd, reg_clr_rd, reg_thresh_wr, reg_clr_wr;
482+
s64 s_thresh, s_hyst, s_clr;
483+
int s_val, thresh, clr, ret;
484+
485+
/* Select threshold registers */
486+
if (dir == IIO_EV_DIR_RISING) {
487+
reg_thresh_rd = HDC3020_R_T_RH_THRESH_HIGH;
488+
reg_thresh_wr = HDC3020_S_T_RH_THRESH_HIGH;
489+
reg_clr_rd = HDC3020_R_T_RH_THRESH_HIGH_CLR;
490+
reg_clr_wr = HDC3020_S_T_RH_THRESH_HIGH_CLR;
394491
} else {
395-
if (dir == IIO_EV_DIR_RISING)
396-
reg = HDC3020_S_T_RH_THRESH_HIGH_CLR;
397-
else
398-
reg = HDC3020_S_T_RH_THRESH_LOW_CLR;
492+
reg_thresh_rd = HDC3020_R_T_RH_THRESH_LOW;
493+
reg_thresh_wr = HDC3020_S_T_RH_THRESH_LOW;
494+
reg_clr_rd = HDC3020_R_T_RH_THRESH_LOW_CLR;
495+
reg_clr_wr = HDC3020_S_T_RH_THRESH_LOW_CLR;
399496
}
400497

401498
guard(mutex)(&data->lock);
402-
ret = hdc3020_read_be16(data, reg);
499+
ret = hdc3020_read_be16(data, reg_thresh_rd);
500+
if (ret < 0)
501+
return ret;
502+
503+
thresh = ret;
504+
ret = hdc3020_read_be16(data, reg_clr_rd);
403505
if (ret < 0)
404506
return ret;
405507

508+
clr = ret;
509+
/* Scale value to include decimal part into calculations */
510+
s_val = (val < 0) ? (val * 1000000 - val2) : (val * 1000000 + val2);
406511
switch (chan->type) {
407512
case IIO_TEMP:
408-
/*
409-
* Calculate temperature threshold, shift it down to get the
410-
* truncated threshold representation in the 9LSBs while keeping
411-
* the current humidity threshold in the 7 MSBs.
412-
*/
413-
tmp = ((u64)(((val + 45) * MICRO) + val2)) * 65535ULL;
414-
tmp = div_u64(tmp, MICRO * 175);
415-
val = tmp >> HDC3020_THRESH_TEMP_TRUNC_SHIFT;
416-
val = FIELD_PREP(HDC3020_THRESH_TEMP_MASK, val);
417-
val |= (FIELD_GET(HDC3020_THRESH_HUM_MASK, ret) <<
418-
HDC3020_THRESH_HUM_TRUNC_SHIFT);
513+
switch (info) {
514+
case IIO_EV_INFO_VALUE:
515+
s_val = max(s_val, HDC3020_MIN_TEMP_MICRO);
516+
s_val = min(s_val, HDC3020_MAX_TEMP_MICRO);
517+
reg = reg_thresh_wr;
518+
reg_val = hdc3020_thresh_set_temp(s_val, thresh);
519+
ret = _hdc3020_write_thresh(data, reg, reg_val);
520+
if (ret < 0)
521+
return ret;
522+
523+
/* Calculate old hysteresis */
524+
s_thresh = (s64)hdc3020_thresh_get_temp(thresh) * 1000000;
525+
s_clr = (s64)hdc3020_thresh_get_temp(clr) * 1000000;
526+
s_hyst = div_s64(abs(s_thresh - s_clr), 65535);
527+
/* Set new threshold */
528+
thresh = reg_val;
529+
/* Set old hysteresis */
530+
s_val = s_hyst;
531+
fallthrough;
532+
case IIO_EV_INFO_HYSTERESIS:
533+
/*
534+
* Function hdc3020_thresh_get_temp returns temperature
535+
* in degree celsius scaled by 65535. Scale by 1000000
536+
* to be able to subtract scaled hysteresis value.
537+
*/
538+
s_thresh = (s64)hdc3020_thresh_get_temp(thresh) * 1000000;
539+
/*
540+
* Units of s_val are in micro degree celsius, scale by
541+
* 65535 to get same units as s_thresh.
542+
*/
543+
s_val = min(abs(s_val), HDC3020_MAX_TEMP_HYST_MICRO);
544+
s_hyst = (s64)s_val * 65535;
545+
s_clr = hdc3020_thresh_clr(s_thresh, s_hyst, dir);
546+
s_clr = max(s_clr, HDC3020_MIN_TEMP_MICRO);
547+
s_clr = min(s_clr, HDC3020_MAX_TEMP_MICRO);
548+
reg = reg_clr_wr;
549+
reg_val = hdc3020_thresh_set_temp(s_clr, clr);
550+
break;
551+
default:
552+
return -EOPNOTSUPP;
553+
}
419554
break;
420555
case IIO_HUMIDITYRELATIVE:
421-
/*
422-
* Calculate humidity threshold, shift it down and up to get the
423-
* truncated threshold representation in the 7MSBs while keeping
424-
* the current temperature threshold in the 9 LSBs.
425-
*/
426-
tmp = ((u64)((val * MICRO) + val2)) * 65535ULL;
427-
tmp = div_u64(tmp, MICRO * 100);
428-
val = tmp >> HDC3020_THRESH_HUM_TRUNC_SHIFT;
429-
val = FIELD_PREP(HDC3020_THRESH_HUM_MASK, val);
430-
val |= FIELD_GET(HDC3020_THRESH_TEMP_MASK, ret);
556+
s_val = (s_val < 0) ? 0 : min(s_val, HDC3020_MAX_HUM_MICRO);
557+
switch (info) {
558+
case IIO_EV_INFO_VALUE:
559+
reg = reg_thresh_wr;
560+
reg_val = hdc3020_thresh_set_hum(s_val, thresh);
561+
ret = _hdc3020_write_thresh(data, reg, reg_val);
562+
if (ret < 0)
563+
return ret;
564+
565+
/* Calculate old hysteresis */
566+
s_thresh = (s64)hdc3020_thresh_get_hum(thresh) * 1000000;
567+
s_clr = (s64)hdc3020_thresh_get_hum(clr) * 1000000;
568+
s_hyst = div_s64(abs(s_thresh - s_clr), 65535);
569+
/* Set new threshold */
570+
thresh = reg_val;
571+
/* Try to set old hysteresis */
572+
s_val = min(abs(s_hyst), HDC3020_MAX_HUM_MICRO);
573+
fallthrough;
574+
case IIO_EV_INFO_HYSTERESIS:
575+
/*
576+
* Function hdc3020_thresh_get_hum returns relative
577+
* humidity in percent scaled by 65535. Scale by 1000000
578+
* to be able to subtract scaled hysteresis value.
579+
*/
580+
s_thresh = (s64)hdc3020_thresh_get_hum(thresh) * 1000000;
581+
/*
582+
* Units of s_val are in micro percent, scale by 65535
583+
* to get same units as s_thresh.
584+
*/
585+
s_hyst = (s64)s_val * 65535;
586+
s_clr = hdc3020_thresh_clr(s_thresh, s_hyst, dir);
587+
s_clr = max(s_clr, 0);
588+
s_clr = min(s_clr, HDC3020_MAX_HUM_MICRO);
589+
reg = reg_clr_wr;
590+
reg_val = hdc3020_thresh_set_hum(s_clr, clr);
591+
break;
592+
default:
593+
return -EOPNOTSUPP;
594+
}
431595
break;
432596
default:
433597
return -EOPNOTSUPP;
434598
}
435599

436-
put_unaligned_be16(reg, buf);
437-
put_unaligned_be16(val, buf + 2);
438-
buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
439-
return hdc3020_write_bytes(data, buf, 5);
600+
return _hdc3020_write_thresh(data, reg, reg_val);
440601
}
441602

442603
static int hdc3020_read_thresh(struct iio_dev *indio_dev,
@@ -447,48 +608,60 @@ static int hdc3020_read_thresh(struct iio_dev *indio_dev,
447608
int *val, int *val2)
448609
{
449610
struct hdc3020_data *data = iio_priv(indio_dev);
450-
u16 reg;
451-
int ret;
611+
u16 reg_thresh, reg_clr;
612+
int thresh, clr, ret;
452613

453-
/* Select threshold register */
454-
if (info == IIO_EV_INFO_VALUE) {
455-
if (dir == IIO_EV_DIR_RISING)
456-
reg = HDC3020_R_T_RH_THRESH_HIGH;
457-
else
458-
reg = HDC3020_R_T_RH_THRESH_LOW;
614+
/* Select threshold registers */
615+
if (dir == IIO_EV_DIR_RISING) {
616+
reg_thresh = HDC3020_R_T_RH_THRESH_HIGH;
617+
reg_clr = HDC3020_R_T_RH_THRESH_HIGH_CLR;
459618
} else {
460-
if (dir == IIO_EV_DIR_RISING)
461-
reg = HDC3020_R_T_RH_THRESH_HIGH_CLR;
462-
else
463-
reg = HDC3020_R_T_RH_THRESH_LOW_CLR;
619+
reg_thresh = HDC3020_R_T_RH_THRESH_LOW;
620+
reg_clr = HDC3020_R_T_RH_THRESH_LOW_CLR;
464621
}
465622

466623
guard(mutex)(&data->lock);
467-
ret = hdc3020_read_be16(data, reg);
624+
ret = hdc3020_read_be16(data, reg_thresh);
468625
if (ret < 0)
469626
return ret;
470627

471628
switch (chan->type) {
472629
case IIO_TEMP:
473-
/*
474-
* Get the temperature threshold from 9 LSBs, shift them to get
475-
* the truncated temperature threshold representation and
476-
* calculate the threshold according to the formula in the
477-
* datasheet.
478-
*/
479-
*val = FIELD_GET(HDC3020_THRESH_TEMP_MASK, ret);
480-
*val = *val << HDC3020_THRESH_TEMP_TRUNC_SHIFT;
481-
*val = -2949075 + (175 * (*val));
630+
thresh = hdc3020_thresh_get_temp(ret);
631+
switch (info) {
632+
case IIO_EV_INFO_VALUE:
633+
*val = thresh;
634+
break;
635+
case IIO_EV_INFO_HYSTERESIS:
636+
ret = hdc3020_read_be16(data, reg_clr);
637+
if (ret < 0)
638+
return ret;
639+
640+
clr = hdc3020_thresh_get_temp(ret);
641+
*val = abs(thresh - clr);
642+
break;
643+
default:
644+
return -EOPNOTSUPP;
645+
}
482646
*val2 = 65535;
483647
return IIO_VAL_FRACTIONAL;
484648
case IIO_HUMIDITYRELATIVE:
485-
/*
486-
* Get the humidity threshold from 7 MSBs, shift them to get the
487-
* truncated humidity threshold representation and calculate the
488-
* threshold according to the formula in the datasheet.
489-
*/
490-
*val = FIELD_GET(HDC3020_THRESH_HUM_MASK, ret);
491-
*val = (*val << HDC3020_THRESH_HUM_TRUNC_SHIFT) * 100;
649+
thresh = hdc3020_thresh_get_hum(ret);
650+
switch (info) {
651+
case IIO_EV_INFO_VALUE:
652+
*val = thresh;
653+
break;
654+
case IIO_EV_INFO_HYSTERESIS:
655+
ret = hdc3020_read_be16(data, reg_clr);
656+
if (ret < 0)
657+
return ret;
658+
659+
clr = hdc3020_thresh_get_hum(ret);
660+
*val = abs(thresh - clr);
661+
break;
662+
default:
663+
return -EOPNOTSUPP;
664+
}
492665
*val2 = 65535;
493666
return IIO_VAL_FRACTIONAL;
494667
default:

0 commit comments

Comments
 (0)