diff mbox series

iio: humidity: hdc3020: add threshold events support

Message ID 20240203090530.53374-1-dima.fedrau@gmail.com (mailing list archive)
State Changes Requested
Headers show
Series iio: humidity: hdc3020: add threshold events support | expand

Commit Message

Dimitri Fedrau Feb. 3, 2024, 9:05 a.m. UTC
Add threshold events support for temperature and relative humidity. To
enable them the higher and lower threshold registers must be programmed
and the higher threshold must be greater then or equal to the lower
threshold. Otherwise the event is disabled. Invalid hysteresis values
are ignored by the device. There is no further configuration possible.

Tested by setting thresholds/hysteresis and turning the heater on/off.
Used iio_event_monitor in tools/iio to catch events while constantly
displaying temperature and humidity values.
Threshold and hysteresis values are cached in the driver, used i2c-tools
to read the threshold and hysteresis values from the device and make
sure cached values are consistent to values written to the device.

Based on Fix:
a69eeaad093d "iio: humidity: hdc3020: fix temperature offset" in branch
fixes-togreg

Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
---
 drivers/iio/humidity/hdc3020.c | 339 +++++++++++++++++++++++++++++++++
 1 file changed, 339 insertions(+)

Comments

Javier Carrasco Feb. 3, 2024, 9:30 a.m. UTC | #1
Hi Dimitri,

On 03.02.24 10:05, Dimitri Fedrau wrote:
> Add threshold events support for temperature and relative humidity. To
> enable them the higher and lower threshold registers must be programmed
> and the higher threshold must be greater then or equal to the lower
> threshold. Otherwise the event is disabled. Invalid hysteresis values
> are ignored by the device. There is no further configuration possible.
> 
> Tested by setting thresholds/hysteresis and turning the heater on/off.
> Used iio_event_monitor in tools/iio to catch events while constantly
> displaying temperature and humidity values.
> Threshold and hysteresis values are cached in the driver, used i2c-tools
> to read the threshold and hysteresis values from the device and make
> sure cached values are consistent to values written to the device.
> 
> Based on Fix:
> a69eeaad093d "iio: humidity: hdc3020: fix temperature offset" in branch
> fixes-togreg
> 
> Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
> ---
>  drivers/iio/humidity/hdc3020.c | 339 +++++++++++++++++++++++++++++++++
>  1 file changed, 339 insertions(+)
> 

...

> +static int hdc3020_write_thresh(struct iio_dev *indio_dev,
> +				const struct iio_chan_spec *chan,
> +				enum iio_event_type type,
> +				enum iio_event_direction dir,
> +				enum iio_event_info info,
> +				int val, int val2)
> +{
> +	struct hdc3020_data *data = iio_priv(indio_dev);
> +	u16 *thresh;
> +	u8 buf[5];
> +	int ret;
> +
> +	/* Supported temperature range is from –40 to 125 degree celsius */
Should that not be val < -40?
> +	if (val < -45 || val > 125)
> +		return -EINVAL;
> +
> +	/* Select threshold and associated register */
> +	if (info == IIO_EV_INFO_VALUE) {
> +		if (dir == IIO_EV_DIR_RISING) {
> +			thresh = &data->t_rh_thresh_high;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_HIGH, 2);
> +		} else {
> +			thresh = &data->t_rh_thresh_low;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_LOW, 2);
> +		}
> +	} else {
> +		if (dir == IIO_EV_DIR_RISING) {
> +			thresh = &data->t_rh_thresh_high_clr;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_HIGH_CLR, 2);
> +		} else {
> +			thresh = &data->t_rh_thresh_low_clr;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_LOW_CLR, 2);
> +		}
> +	}
> +
> +	guard(mutex)(&data->lock);
> +	switch (chan->type) {
> +	case IIO_TEMP:
> +		/*
> +		 * Store truncated temperature threshold into 9 LSBs while
> +		 * keeping the old humidity threshold in the 7 MSBs.
> +		 
> +		val = (((val + 45) * 65535 / 175) >> HDC3020_THRESH_TEMP_SHIFT);
> +		val &= HDC3020_THRESH_TEMP_MASK;
> +		val |= (*thresh & HDC3020_THRESH_HUM_MASK);
> +		break;
> +	case IIO_HUMIDITYRELATIVE:
> +		/*
> +		 * Store truncated humidity threshold into 7 MSBs while
> +		 * keeping the old temperature threshold in the 9 LSBs.
> +		 */
> +		val = ((val * 65535 / 100) & HDC3020_THRESH_HUM_MASK);
> +		val |= (*thresh & HDC3020_THRESH_TEMP_MASK);
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	put_unaligned_be16(val, &buf[2]);
> +	buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
> +	ret = hdc3020_write_bytes(data, buf, 5);
> +	if (ret)
> +		return ret;
> +
> +	/* Update threshold */
> +	*thresh = val;
> +
> +	return 0;
> +}
> +
> 

Best regards,
Javier Carrasco
Christophe JAILLET Feb. 3, 2024, 9:58 a.m. UTC | #2
Le 03/02/2024 à 10:05, Dimitri Fedrau a écrit :
> Add threshold events support for temperature and relative humidity. To
> enable them the higher and lower threshold registers must be programmed
> and the higher threshold must be greater then or equal to the lower
> threshold. Otherwise the event is disabled. Invalid hysteresis values
> are ignored by the device. There is no further configuration possible.
> 
> Tested by setting thresholds/hysteresis and turning the heater on/off.
> Used iio_event_monitor in tools/iio to catch events while constantly
> displaying temperature and humidity values.
> Threshold and hysteresis values are cached in the driver, used i2c-tools
> to read the threshold and hysteresis values from the device and make
> sure cached values are consistent to values written to the device.
> 
> Based on Fix:
> a69eeaad093d "iio: humidity: hdc3020: fix temperature offset" in branch
> fixes-togreg
> 
> Signed-off-by: Dimitri Fedrau <dima.fedrau-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
>   drivers/iio/humidity/hdc3020.c | 339 +++++++++++++++++++++++++++++++++
>   1 file changed, 339 insertions(+)
> 
> diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c
> index ed70415512f6..1cdff7af4ca8 100644
> --- a/drivers/iio/humidity/hdc3020.c
> +++ b/drivers/iio/humidity/hdc3020.c
> @@ -16,16 +16,27 @@
>   #include <linux/init.h>
>   #include <linux/module.h>
>   #include <linux/mutex.h>
> +#include <linux/interrupt.h>

Nit: alphabetical order could be kept

>   
>   #include <asm/unaligned.h>
>   
>   #include <linux/iio/iio.h>
> +#include <linux/iio/events.h>

Nit: same

...

>   
> +static const u8 HDC3020_R_T_RH_THRESH_LOW[2] = { 0xE1, 0x02 };
> +static const u8 HDC3020_R_R_RH_THRESH_LOW_CLR[2] = { 0xE1, 0x09 };

I don't know what the R and T are for, but shoukld this be 
HDC3020_R_T_RH_THRESH_LOW_CLR to match other adjacent line?

> +static const u8 HDC3020_R_T_RH_THRESH_HIGH_CLR[2] = { 0xE1, 0x14 };
> +static const u8 HDC3020_R_T_RH_THRESH_HIGH[2] = { 0xE1, 0x1F };

...

> +static int hdc3020_write_thresh(struct iio_dev *indio_dev,
> +				const struct iio_chan_spec *chan,
> +				enum iio_event_type type,
> +				enum iio_event_direction dir,
> +				enum iio_event_info info,
> +				int val, int val2)
> +{
> +	struct hdc3020_data *data = iio_priv(indio_dev);
> +	u16 *thresh;
> +	u8 buf[5];
> +	int ret;
> +
> +	/* Supported temperature range is from –40 to 125 degree celsius */
> +	if (val < -45 || val > 125)
> +		return -EINVAL;
> +
> +	/* Select threshold and associated register */
> +	if (info == IIO_EV_INFO_VALUE) {
> +		if (dir == IIO_EV_DIR_RISING) {
> +			thresh = &data->t_rh_thresh_high;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_HIGH, 2);
> +		} else {
> +			thresh = &data->t_rh_thresh_low;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_LOW, 2);
> +		}
> +	} else {
> +		if (dir == IIO_EV_DIR_RISING) {
> +			thresh = &data->t_rh_thresh_high_clr;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_HIGH_CLR, 2);
> +		} else {
> +			thresh = &data->t_rh_thresh_low_clr;
> +			memcpy(buf, HDC3020_S_T_RH_THRESH_LOW_CLR, 2);
> +		}
> +	}
> +
> +	guard(mutex)(&data->lock);
> +	switch (chan->type) {
> +	case IIO_TEMP:
> +		/*
> +		 * Store truncated temperature threshold into 9 LSBs while
> +		 * keeping the old humidity threshold in the 7 MSBs.
> +		 */
> +		val = (((val + 45) * 65535 / 175) >> HDC3020_THRESH_TEMP_SHIFT);

Why 175?
If the span is -40/+120, I guess it should be 160 and if it is -45/+120, 
165. No?

Maybe something like:
   #define MIN_TEMP -45 (or -40)
   #define MAX_TEMP 120
in order to avoid hard coded constant?

> +		val &= HDC3020_THRESH_TEMP_MASK;
> +		val |= (*thresh & HDC3020_THRESH_HUM_MASK);
> +		break;
> +	case IIO_HUMIDITYRELATIVE:
> +		/*
> +		 * Store truncated humidity threshold into 7 MSBs while
> +		 * keeping the old temperature threshold in the 9 LSBs.
> +		 */
> +		val = ((val * 65535 / 100) & HDC3020_THRESH_HUM_MASK);
> +		val |= (*thresh & HDC3020_THRESH_TEMP_MASK);
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	put_unaligned_be16(val, &buf[2]);
> +	buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
> +	ret = hdc3020_write_bytes(data, buf, 5);
> +	if (ret)
> +		return ret;
> +
> +	/* Update threshold */
> +	*thresh = val;
> +
> +	return 0;
> +}

CJ
Javier Carrasco Feb. 3, 2024, 10:06 a.m. UTC | #3
On 03.02.24 10:58, Christophe JAILLET wrote:
> Le 03/02/2024 à 10:05, Dimitri Fedrau a écrit :
>> Add threshold events support for temperature and relative humidity. To
>> enable them the higher and lower threshold registers must be programmed
>> and the higher threshold must be greater then or equal to the lower
>> threshold. Otherwise the event is disabled. Invalid hysteresis values
>> are ignored by the device. There is no further configuration possible.
>>
>> Tested by setting thresholds/hysteresis and turning the heater on/off.
>> Used iio_event_monitor in tools/iio to catch events while constantly
>> displaying temperature and humidity values.
>> Threshold and hysteresis values are cached in the driver, used i2c-tools
>> to read the threshold and hysteresis values from the device and make
>> sure cached values are consistent to values written to the device.
>>
>> Based on Fix:
>> a69eeaad093d "iio: humidity: hdc3020: fix temperature offset" in branch
>> fixes-togreg
>>
>> Signed-off-by: Dimitri Fedrau
>> <dima.fedrau-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> ---
>>   drivers/iio/humidity/hdc3020.c | 339 +++++++++++++++++++++++++++++++++
>>   1 file changed, 339 insertions(+)

...

>> +    guard(mutex)(&data->lock);
>> +    switch (chan->type) {
>> +    case IIO_TEMP:
>> +        /*
>> +         * Store truncated temperature threshold into 9 LSBs while
>> +         * keeping the old humidity threshold in the 7 MSBs.
>> +         */
>> +        val = (((val + 45) * 65535 / 175) >> HDC3020_THRESH_TEMP_SHIFT);
> 
> Why 175?
> If the span is -40/+120, I guess it should be 160 and if it is -45/+120,
> 165. No?
> 
> Maybe something like:
>   #define MIN_TEMP -45 (or -40)
>   #define MAX_TEMP 120
> in order to avoid hard coded constant?
> 

the 45 and 175 values come from the conversion formula provided in the
datasheet (page 13), even though the sensor range is from –40°C to 125°C.

>> +        val &= HDC3020_THRESH_TEMP_MASK;
>> +        val |= (*thresh & HDC3020_THRESH_HUM_MASK);
>> +        break;
>> +    case IIO_HUMIDITYRELATIVE:
>> +        /*
>> +         * Store truncated humidity threshold into 7 MSBs while
>> +         * keeping the old temperature threshold in the 9 LSBs.
>> +         */
>> +        val = ((val * 65535 / 100) & HDC3020_THRESH_HUM_MASK);
>> +        val |= (*thresh & HDC3020_THRESH_TEMP_MASK);
>> +        break;
>> +    default:
>> +        return -EOPNOTSUPP;
>> +    }
>> +
>> +    put_unaligned_be16(val, &buf[2]);
>> +    buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
>> +    ret = hdc3020_write_bytes(data, buf, 5);
>> +    if (ret)
>> +        return ret;
>> +
>> +    /* Update threshold */
>> +    *thresh = val;
>> +
>> +    return 0;
>> +}
> 
> CJ
> 

Best regards,
Javier Carrasco
Dimitri Fedrau Feb. 3, 2024, 2:42 p.m. UTC | #4
Am Sat, Feb 03, 2024 at 10:30:09AM +0100 schrieb Javier Carrasco:
> Hi Dimitri,
> 
Hi Javier,

> > +	/* Supported temperature range is from –40 to 125 degree celsius */
> Should that not be val < -40?
yes, you are right. Will fix it.
> > +	if (val < -45 || val > 125)
> > +		return -EINVAL;
> > +

Best regards,
Dimitri
Dimitri Fedrau Feb. 3, 2024, 2:47 p.m. UTC | #5
Am Sat, Feb 03, 2024 at 10:58:24AM +0100 schrieb Christophe JAILLET:
> Le 03/02/2024 à 10:05, Dimitri Fedrau a écrit :
>[...]
> > diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c
> > index ed70415512f6..1cdff7af4ca8 100644
> > --- a/drivers/iio/humidity/hdc3020.c
> > +++ b/drivers/iio/humidity/hdc3020.c
> > @@ -16,16 +16,27 @@
> >   #include <linux/init.h>
> >   #include <linux/module.h>
> >   #include <linux/mutex.h>
> > +#include <linux/interrupt.h>
> 
> Nit: alphabetical order could be kept
>
Will fix it.
> >   #include <asm/unaligned.h>
> >   #include <linux/iio/iio.h>
> > +#include <linux/iio/events.h>
> 
> Nit: same
> 
> ...
> 
Will fix it.
> > +static const u8 HDC3020_R_T_RH_THRESH_LOW[2] = { 0xE1, 0x02 };
> > +static const u8 HDC3020_R_R_RH_THRESH_LOW_CLR[2] = { 0xE1, 0x09 };
> 
> I don't know what the R and T are for, but shoukld this be
> HDC3020_R_T_RH_THRESH_LOW_CLR to match other adjacent line?
>
You are right, should match the others. Will fix it.
> > +static const u8 HDC3020_R_T_RH_THRESH_HIGH_CLR[2] = { 0xE1, 0x14 };
> > +static const u8 HDC3020_R_T_RH_THRESH_HIGH[2] = { 0xE1, 0x1F };
> 
> ...
>
>[...]
> Maybe something like:
>   #define MIN_TEMP -45 (or -40)
>   #define MAX_TEMP 120
> in order to avoid hard coded constant?
> 
Will add the constants, thanks.
>[...]

Best regards,
Dimitri
Dimitri Fedrau Feb. 3, 2024, 2:54 p.m. UTC | #6
Am Sat, Feb 03, 2024 at 11:06:02AM +0100 schrieb Javier Carrasco:
> On 03.02.24 10:58, Christophe JAILLET wrote:
> > Le 03/02/2024 à 10:05, Dimitri Fedrau a écrit :
> >> [...]
> >>   drivers/iio/humidity/hdc3020.c | 339 +++++++++++++++++++++++++++++++++
> >>   1 file changed, 339 insertions(+)
> 
> ...
> 
> >> +    guard(mutex)(&data->lock);
> >> +    switch (chan->type) {
> >> +    case IIO_TEMP:
> >> +        /*
> >> +         * Store truncated temperature threshold into 9 LSBs while
> >> +         * keeping the old humidity threshold in the 7 MSBs.
> >> +         */
> >> +        val = (((val + 45) * 65535 / 175) >> HDC3020_THRESH_TEMP_SHIFT);
> > 
> > Why 175?
> > If the span is -40/+120, I guess it should be 160 and if it is -45/+120,
> > 165. No?
> > 
> > Maybe something like:
> >   #define MIN_TEMP -45 (or -40)
> >   #define MAX_TEMP 120
> > in order to avoid hard coded constant?
> > 
> 
> the 45 and 175 values come from the conversion formula provided in the
> datasheet (page 13), even though the sensor range is from –40°C to 125°C.
> 
Will add following constants:
#define MIN_TEMP -40
#define MAX_TEMP 125

It's the supported temperature range by the chip as Javier already
explained. Thanks for finding this.
> >> [...]

Best regards,
Dimitri
Javier Carrasco Feb. 3, 2024, 3:53 p.m. UTC | #7
On 03.02.24 15:42, Dimitri Fedrau wrote:
> Am Sat, Feb 03, 2024 at 10:30:09AM +0100 schrieb Javier Carrasco:
>> Hi Dimitri,
>>
> Hi Javier,
> 
>>> +	/* Supported temperature range is from –40 to 125 degree celsius */
>> Should that not be val < -40?
> yes, you are right. Will fix it.
>>> +	if (val < -45 || val > 125)
>>> +		return -EINVAL;
>>> +
> 
> Best regards,
> Dimitri

When at it, could you please rename the hdc3020_id variable you added to
the probe function? It shadows the i2c_device_id global variable (it is
not used in the probe function, but there is no need to use the exact
same name), and given that it is in the hdc3020_probe function,
mentioning the device name again is kind of redundant. Something like
just "id" or "dev_id" would suffice.

Best regards,
Javier Carrasco
Dimitri Fedrau Feb. 3, 2024, 4:08 p.m. UTC | #8
Am Sat, Feb 03, 2024 at 04:53:33PM +0100 schrieb Javier Carrasco:
> On 03.02.24 15:42, Dimitri Fedrau wrote:
> > Am Sat, Feb 03, 2024 at 10:30:09AM +0100 schrieb Javier Carrasco:
> >> Hi Dimitri,
> >>
> > Hi Javier,
> > 
> >>> +	/* Supported temperature range is from –40 to 125 degree celsius */
> >> Should that not be val < -40?
> > yes, you are right. Will fix it.
> >>> +	if (val < -45 || val > 125)
> >>> +		return -EINVAL;
> >>> +
> > 
> > Best regards,
> > Dimitri
> 
> When at it, could you please rename the hdc3020_id variable you added to
> the probe function? It shadows the i2c_device_id global variable (it is
> not used in the probe function, but there is no need to use the exact
> same name), and given that it is in the hdc3020_probe function,
> mentioning the device name again is kind of redundant. Something like
> just "id" or "dev_id" would suffice.
>
Sure.

Best regards,
Dimitri
diff mbox series

Patch

diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c
index ed70415512f6..1cdff7af4ca8 100644
--- a/drivers/iio/humidity/hdc3020.c
+++ b/drivers/iio/humidity/hdc3020.c
@@ -16,16 +16,27 @@ 
 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/interrupt.h>
 
 #include <asm/unaligned.h>
 
 #include <linux/iio/iio.h>
+#include <linux/iio/events.h>
 
 #define HDC3020_HEATER_CMD_MSB		0x30 /* shared by all heater commands */
 #define HDC3020_HEATER_ENABLE		0x6D
 #define HDC3020_HEATER_DISABLE		0x66
 #define HDC3020_HEATER_CONFIG		0x6E
 
+#define HDC3020_THRESH_TEMP_MASK	GENMASK(8, 0)
+#define HDC3020_THRESH_TEMP_SHIFT	7
+#define HDC3020_THRESH_HUM_MASK		GENMASK(15, 9)
+
+#define HDC3020_STATUS_T_LOW_ALERT	BIT(6)
+#define HDC3020_STATUS_T_HIGH_ALERT	BIT(7)
+#define HDC3020_STATUS_RH_LOW_ALERT	BIT(8)
+#define HDC3020_STATUS_RH_HIGH_ALERT	BIT(9)
+
 #define HDC3020_READ_RETRY_TIMES	10
 #define HDC3020_BUSY_DELAY_MS		10
 
@@ -33,14 +44,28 @@ 
 
 static const u8 HDC3020_S_AUTO_10HZ_MOD0[2] = { 0x27, 0x37 };
 
+static const u8 HDC3020_S_STATUS[2] = { 0x30, 0x41 };
+
 static const u8 HDC3020_EXIT_AUTO[2] = { 0x30, 0x93 };
 
+static const u8 HDC3020_S_T_RH_THRESH_LOW[2] = { 0x61, 0x00 };
+static const u8 HDC3020_S_T_RH_THRESH_LOW_CLR[2] = { 0x61, 0x0B };
+static const u8 HDC3020_S_T_RH_THRESH_HIGH_CLR[2] = { 0x61, 0x16 };
+static const u8 HDC3020_S_T_RH_THRESH_HIGH[2] = { 0x61, 0x1D };
+
 static const u8 HDC3020_R_T_RH_AUTO[2] = { 0xE0, 0x00 };
 static const u8 HDC3020_R_T_LOW_AUTO[2] = { 0xE0, 0x02 };
 static const u8 HDC3020_R_T_HIGH_AUTO[2] = { 0xE0, 0x03 };
 static const u8 HDC3020_R_RH_LOW_AUTO[2] = { 0xE0, 0x04 };
 static const u8 HDC3020_R_RH_HIGH_AUTO[2] = { 0xE0, 0x05 };
 
+static const u8 HDC3020_R_T_RH_THRESH_LOW[2] = { 0xE1, 0x02 };
+static const u8 HDC3020_R_R_RH_THRESH_LOW_CLR[2] = { 0xE1, 0x09 };
+static const u8 HDC3020_R_T_RH_THRESH_HIGH_CLR[2] = { 0xE1, 0x14 };
+static const u8 HDC3020_R_T_RH_THRESH_HIGH[2] = { 0xE1, 0x1F };
+
+static const u8 HDC3020_R_STATUS[2] = { 0xF3, 0x2D };
+
 struct hdc3020_data {
 	struct i2c_client *client;
 	/*
@@ -50,22 +75,54 @@  struct hdc3020_data {
 	 * if the device does not respond).
 	 */
 	struct mutex lock;
+	/*
+	 * Temperature and humidity thresholds are packed together into a single
+	 * 16 bit value. Each threshold is represented by a truncated 16 bit
+	 * value. The 7 MSBs of a relative humidity threshold are concatenated
+	 * with the 9 MSBs of a temperature threshold, where the temperature
+	 * threshold resides in the 7 LSBs. Due to the truncated representation,
+	 * there is a resolution loss of 0.5 degree celsius in temperature and a
+	 * 1% resolution loss in relative humidity.
+	 */
+	u16 t_rh_thresh_low;
+	u16 t_rh_thresh_high;
+	u16 t_rh_thresh_low_clr;
+	u16 t_rh_thresh_high_clr;
 };
 
 static const int hdc3020_heater_vals[] = {0, 1, 0x3FFF};
 
+static const struct iio_event_spec hdc3020_t_rh_event[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+		BIT(IIO_EV_INFO_HYSTERESIS),
+	},
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
+		BIT(IIO_EV_INFO_HYSTERESIS),
+	},
+};
+
 static const struct iio_chan_spec hdc3020_channels[] = {
 	{
 		.type = IIO_TEMP,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 		BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PEAK) |
 		BIT(IIO_CHAN_INFO_TROUGH) | BIT(IIO_CHAN_INFO_OFFSET),
+		.event_spec = hdc3020_t_rh_event,
+		.num_event_specs = ARRAY_SIZE(hdc3020_t_rh_event),
 	},
 	{
 		.type = IIO_HUMIDITYRELATIVE,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 		BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PEAK) |
 		BIT(IIO_CHAN_INFO_TROUGH),
+		.event_spec = hdc3020_t_rh_event,
+		.num_event_specs = ARRAY_SIZE(hdc3020_t_rh_event),
 	},
 	{
 		/*
@@ -389,10 +446,241 @@  static int hdc3020_write_raw(struct iio_dev *indio_dev,
 	return -EINVAL;
 }
 
+static int hdc3020_write_thresh(struct iio_dev *indio_dev,
+				const struct iio_chan_spec *chan,
+				enum iio_event_type type,
+				enum iio_event_direction dir,
+				enum iio_event_info info,
+				int val, int val2)
+{
+	struct hdc3020_data *data = iio_priv(indio_dev);
+	u16 *thresh;
+	u8 buf[5];
+	int ret;
+
+	/* Supported temperature range is from –40 to 125 degree celsius */
+	if (val < -45 || val > 125)
+		return -EINVAL;
+
+	/* Select threshold and associated register */
+	if (info == IIO_EV_INFO_VALUE) {
+		if (dir == IIO_EV_DIR_RISING) {
+			thresh = &data->t_rh_thresh_high;
+			memcpy(buf, HDC3020_S_T_RH_THRESH_HIGH, 2);
+		} else {
+			thresh = &data->t_rh_thresh_low;
+			memcpy(buf, HDC3020_S_T_RH_THRESH_LOW, 2);
+		}
+	} else {
+		if (dir == IIO_EV_DIR_RISING) {
+			thresh = &data->t_rh_thresh_high_clr;
+			memcpy(buf, HDC3020_S_T_RH_THRESH_HIGH_CLR, 2);
+		} else {
+			thresh = &data->t_rh_thresh_low_clr;
+			memcpy(buf, HDC3020_S_T_RH_THRESH_LOW_CLR, 2);
+		}
+	}
+
+	guard(mutex)(&data->lock);
+	switch (chan->type) {
+	case IIO_TEMP:
+		/*
+		 * Store truncated temperature threshold into 9 LSBs while
+		 * keeping the old humidity threshold in the 7 MSBs.
+		 */
+		val = (((val + 45) * 65535 / 175) >> HDC3020_THRESH_TEMP_SHIFT);
+		val &= HDC3020_THRESH_TEMP_MASK;
+		val |= (*thresh & HDC3020_THRESH_HUM_MASK);
+		break;
+	case IIO_HUMIDITYRELATIVE:
+		/*
+		 * Store truncated humidity threshold into 7 MSBs while
+		 * keeping the old temperature threshold in the 9 LSBs.
+		 */
+		val = ((val * 65535 / 100) & HDC3020_THRESH_HUM_MASK);
+		val |= (*thresh & HDC3020_THRESH_TEMP_MASK);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	put_unaligned_be16(val, &buf[2]);
+	buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
+	ret = hdc3020_write_bytes(data, buf, 5);
+	if (ret)
+		return ret;
+
+	/* Update threshold */
+	*thresh = val;
+
+	return 0;
+}
+
+static int _hdc3020_read_thresh(struct hdc3020_data *data,
+				enum iio_event_info info,
+				enum iio_event_direction dir, u16 *thresh)
+{
+	u8 crc, buf[3];
+	const u8 *cmd;
+	int ret;
+
+	if (info == IIO_EV_INFO_VALUE) {
+		if (dir == IIO_EV_DIR_RISING)
+			cmd = HDC3020_R_T_RH_THRESH_HIGH;
+		else
+			cmd = HDC3020_R_T_RH_THRESH_LOW;
+	} else {
+		if (dir == IIO_EV_DIR_RISING)
+			cmd = HDC3020_R_T_RH_THRESH_HIGH_CLR;
+		else
+			cmd = HDC3020_R_R_RH_THRESH_LOW_CLR;
+	}
+
+	ret = hdc3020_read_bytes(data, cmd, buf, 3);
+	if (ret < 0)
+		return ret;
+
+	/* CRC check of the threshold */
+	crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE);
+	if (crc != buf[2])
+		return -EINVAL;
+
+	*thresh = get_unaligned_be16(buf);
+
+	return 0;
+}
+
+static int hdc3020_read_thresh(struct iio_dev *indio_dev,
+			       const struct iio_chan_spec *chan,
+			       enum iio_event_type type,
+			       enum iio_event_direction dir,
+			       enum iio_event_info info,
+			       int *val, int *val2)
+{
+	struct hdc3020_data *data = iio_priv(indio_dev);
+	u16 *thresh;
+
+	/* Select threshold */
+	if (info == IIO_EV_INFO_VALUE) {
+		if (dir == IIO_EV_DIR_RISING)
+			thresh = &data->t_rh_thresh_high;
+		else
+			thresh = &data->t_rh_thresh_low;
+	} else {
+		if (dir == IIO_EV_DIR_RISING)
+			thresh = &data->t_rh_thresh_high_clr;
+		else
+			thresh = &data->t_rh_thresh_low_clr;
+	}
+
+	guard(mutex)(&data->lock);
+	switch (chan->type) {
+	case IIO_TEMP:
+		/* Get the truncated temperature threshold from 9 LSBs,
+		 * shift them and calculate the threshold according to the
+		 * formula in the datasheet.
+		 */
+		*val = ((*thresh) & HDC3020_THRESH_TEMP_MASK) <<
+			HDC3020_THRESH_TEMP_SHIFT;
+		*val = -2949075 + (175 * (*val));
+		*val2 = 65535;
+		break;
+	case IIO_HUMIDITYRELATIVE:
+		/* Get the truncated humidity threshold from 7 MSBs, and
+		 * calculate the threshold according to the formula in the
+		 * datasheet.
+		 */
+		*val = 100 * ((*thresh) & HDC3020_THRESH_HUM_MASK);
+		*val2 = 65535;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return IIO_VAL_FRACTIONAL;
+}
+
+static int hdc3020_clear_status(struct hdc3020_data *data)
+{
+	return hdc3020_write_bytes(data, HDC3020_S_STATUS, 2);
+}
+
+static int hdc3020_read_status(struct hdc3020_data *data, u16 *stat)
+{
+	u8 crc, buf[3];
+	int ret;
+
+	ret = hdc3020_read_bytes(data, HDC3020_R_STATUS, buf, 3);
+	if (ret < 0)
+		return ret;
+
+	/* CRC check of the status */
+	crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE);
+	if (crc != buf[2])
+		return -EINVAL;
+
+	*stat = get_unaligned_be16(buf);
+
+	return 0;
+}
+
+static irqreturn_t hdc3020_interrupt_handler(int irq, void *private)
+{
+	struct iio_dev *indio_dev = private;
+	struct hdc3020_data *data;
+	u16 stat;
+	int ret;
+
+	data = iio_priv(indio_dev);
+	ret = hdc3020_read_status(data, &stat);
+	if (ret)
+		return IRQ_NONE;
+
+	if (!(stat & (HDC3020_STATUS_T_HIGH_ALERT | HDC3020_STATUS_T_LOW_ALERT |
+		HDC3020_STATUS_RH_HIGH_ALERT | HDC3020_STATUS_RH_LOW_ALERT)))
+		return IRQ_NONE;
+
+	if (stat & HDC3020_STATUS_T_HIGH_ALERT)
+		iio_push_event(indio_dev,
+			       IIO_MOD_EVENT_CODE(IIO_TEMP, 0,
+						  IIO_NO_MOD,
+						  IIO_EV_TYPE_THRESH,
+						  IIO_EV_DIR_RISING),
+						  iio_get_time_ns(indio_dev));
+
+	if (stat & HDC3020_STATUS_T_LOW_ALERT)
+		iio_push_event(indio_dev,
+			       IIO_MOD_EVENT_CODE(IIO_TEMP, 0,
+						  IIO_NO_MOD,
+						  IIO_EV_TYPE_THRESH,
+						  IIO_EV_DIR_FALLING),
+						  iio_get_time_ns(indio_dev));
+
+	if (stat & HDC3020_STATUS_RH_HIGH_ALERT)
+		iio_push_event(indio_dev,
+			       IIO_MOD_EVENT_CODE(IIO_HUMIDITYRELATIVE, 0,
+						  IIO_NO_MOD,
+						  IIO_EV_TYPE_THRESH,
+						  IIO_EV_DIR_RISING),
+						  iio_get_time_ns(indio_dev));
+
+	if (stat & HDC3020_STATUS_RH_LOW_ALERT)
+		iio_push_event(indio_dev,
+			       IIO_MOD_EVENT_CODE(IIO_HUMIDITYRELATIVE, 0,
+						  IIO_NO_MOD,
+						  IIO_EV_TYPE_THRESH,
+						  IIO_EV_DIR_FALLING),
+						  iio_get_time_ns(indio_dev));
+
+	return IRQ_HANDLED;
+}
+
 static const struct iio_info hdc3020_info = {
 	.read_raw = hdc3020_read_raw,
 	.write_raw = hdc3020_write_raw,
 	.read_avail = hdc3020_read_available,
+	.read_event_value = hdc3020_read_thresh,
+	.write_event_value = hdc3020_write_thresh,
 };
 
 static void hdc3020_stop(void *data)
@@ -402,6 +690,7 @@  static void hdc3020_stop(void *data)
 
 static int hdc3020_probe(struct i2c_client *client)
 {
+	const struct i2c_device_id *hdc3020_id;
 	struct iio_dev *indio_dev;
 	struct hdc3020_data *data;
 	int ret;
@@ -413,6 +702,8 @@  static int hdc3020_probe(struct i2c_client *client)
 	if (!indio_dev)
 		return -ENOMEM;
 
+	hdc3020_id = i2c_client_get_device_id(client);
+
 	data = iio_priv(indio_dev);
 	data->client = client;
 	mutex_init(&data->lock);
@@ -425,6 +716,54 @@  static int hdc3020_probe(struct i2c_client *client)
 	indio_dev->channels = hdc3020_channels;
 	indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels);
 
+	/* Read out upper and lower thresholds and hysteresis, which can be the
+	 * default values or values programmed into non-volatile memory.
+	 */
+	ret = _hdc3020_read_thresh(data, IIO_EV_INFO_VALUE, IIO_EV_DIR_FALLING,
+				   &data->t_rh_thresh_low);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Unable to get low thresholds\n");
+
+	ret = _hdc3020_read_thresh(data, IIO_EV_INFO_VALUE, IIO_EV_DIR_RISING,
+				   &data->t_rh_thresh_high);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Unable to get high thresholds\n");
+
+	ret = _hdc3020_read_thresh(data, IIO_EV_INFO_HYSTERESIS,
+				   IIO_EV_DIR_FALLING,
+				   &data->t_rh_thresh_low_clr);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Unable to get low hysteresis\n");
+
+	ret = _hdc3020_read_thresh(data, IIO_EV_INFO_HYSTERESIS,
+				   IIO_EV_DIR_RISING,
+				   &data->t_rh_thresh_high_clr);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Unable to get high hysteresis\n");
+
+	if (client->irq) {
+		/* The alert output is activated by default upon power up, hardware
+		 * reset, and soft reset. Clear the status register before enabling
+		 * the interrupt.
+		 */
+		ret = hdc3020_clear_status(data);
+		if (ret)
+			return ret;
+
+		ret = devm_request_threaded_irq(&client->dev, client->irq,
+						NULL, hdc3020_interrupt_handler,
+						IRQF_TRIGGER_RISING |
+						IRQF_ONESHOT,
+						hdc3020_id->name, indio_dev);
+		if (ret)
+			return dev_err_probe(&client->dev, ret,
+					     "Failed to request IRQ\n");
+	}
+
 	ret = hdc3020_write_bytes(data, HDC3020_S_AUTO_10HZ_MOD0, 2);
 	if (ret)
 		return dev_err_probe(&client->dev, ret,