diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c index e42efe666e2267..11ede97a31d79f 100644 --- a/drivers/iio/humidity/hdc3020.c +++ b/drivers/iio/humidity/hdc3020.c @@ -5,38 +5,67 @@ * * Copyright (C) 2023 * + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + * * Datasheet: https://www.ti.com/lit/ds/symlink/hdc3020.pdf */ +#include #include #include #include #include #include #include +#include #include #include +#include #include +#include #include #define HDC3020_S_AUTO_10HZ_MOD0 0x2737 +#define HDC3020_S_STATUS 0x3041 #define HDC3020_HEATER_DISABLE 0x3066 #define HDC3020_HEATER_ENABLE 0x306D #define HDC3020_HEATER_CONFIG 0x306E #define HDC3020_EXIT_AUTO 0x3093 +#define HDC3020_S_T_RH_THRESH_LOW 0x6100 +#define HDC3020_S_T_RH_THRESH_LOW_CLR 0x610B +#define HDC3020_S_T_RH_THRESH_HIGH_CLR 0x6116 +#define HDC3020_S_T_RH_THRESH_HIGH 0x611D #define HDC3020_R_T_RH_AUTO 0xE000 #define HDC3020_R_T_LOW_AUTO 0xE002 #define HDC3020_R_T_HIGH_AUTO 0xE003 #define HDC3020_R_RH_LOW_AUTO 0xE004 #define HDC3020_R_RH_HIGH_AUTO 0xE005 +#define HDC3020_R_T_RH_THRESH_LOW 0xE102 +#define HDC3020_R_T_RH_THRESH_LOW_CLR 0xE109 +#define HDC3020_R_T_RH_THRESH_HIGH_CLR 0xE114 +#define HDC3020_R_T_RH_THRESH_HIGH 0xE11F +#define HDC3020_R_STATUS 0xF32D + +#define HDC3020_THRESH_TEMP_MASK GENMASK(8, 0) +#define HDC3020_THRESH_TEMP_TRUNC_SHIFT 7 +#define HDC3020_THRESH_HUM_MASK GENMASK(15, 9) +#define HDC3020_THRESH_HUM_TRUNC_SHIFT 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 #define HDC3020_CRC8_POLYNOMIAL 0x31 +#define HDC3020_MIN_TEMP -40 +#define HDC3020_MAX_TEMP 125 + struct hdc3020_data { struct i2c_client *client; /* @@ -50,18 +79,37 @@ struct hdc3020_data { 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), }, { /* @@ -315,10 +363,192 @@ 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); + u8 buf[5]; + u64 tmp; + u16 reg; + int ret; + + /* Supported temperature range is from –40 to 125 degree celsius */ + if (val < HDC3020_MIN_TEMP || val > HDC3020_MAX_TEMP) + return -EINVAL; + + /* Select threshold register */ + 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 { + if (dir == IIO_EV_DIR_RISING) + reg = HDC3020_S_T_RH_THRESH_HIGH_CLR; + else + reg = HDC3020_S_T_RH_THRESH_LOW_CLR; + } + + guard(mutex)(&data->lock); + ret = hdc3020_read_be16(data, reg); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_TEMP: + /* + * 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. + */ + tmp = ((u64)(((val + 45) * MICRO) + val2)) * 65535ULL; + tmp = div_u64(tmp, MICRO * 175); + val = tmp >> HDC3020_THRESH_TEMP_TRUNC_SHIFT; + val = FIELD_PREP(HDC3020_THRESH_TEMP_MASK, val); + val |= (FIELD_GET(HDC3020_THRESH_HUM_MASK, ret) << + HDC3020_THRESH_HUM_TRUNC_SHIFT); + break; + case IIO_HUMIDITYRELATIVE: + /* + * 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. + */ + tmp = ((u64)((val * MICRO) + val2)) * 65535ULL; + tmp = div_u64(tmp, MICRO * 100); + val = tmp >> HDC3020_THRESH_HUM_TRUNC_SHIFT; + val = FIELD_PREP(HDC3020_THRESH_HUM_MASK, val); + val |= FIELD_GET(HDC3020_THRESH_TEMP_MASK, ret); + break; + default: + return -EOPNOTSUPP; + } + + 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_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 reg; + int ret; + + /* Select threshold register */ + if (info == IIO_EV_INFO_VALUE) { + if (dir == IIO_EV_DIR_RISING) + reg = HDC3020_R_T_RH_THRESH_HIGH; + else + reg = HDC3020_R_T_RH_THRESH_LOW; + } else { + if (dir == IIO_EV_DIR_RISING) + reg = HDC3020_R_T_RH_THRESH_HIGH_CLR; + else + reg = HDC3020_R_T_RH_THRESH_LOW_CLR; + } + + guard(mutex)(&data->lock); + ret = hdc3020_read_be16(data, reg); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_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. + */ + *val = FIELD_GET(HDC3020_THRESH_TEMP_MASK, ret); + *val = *val << HDC3020_THRESH_TEMP_TRUNC_SHIFT; + *val = -2949075 + (175 * (*val)); + *val2 = 65535; + return IIO_VAL_FRACTIONAL; + case IIO_HUMIDITYRELATIVE: + /* + * 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. + */ + *val = FIELD_GET(HDC3020_THRESH_HUM_MASK, ret); + *val = (*val << HDC3020_THRESH_HUM_TRUNC_SHIFT) * 100; + *val2 = 65535; + return IIO_VAL_FRACTIONAL; + default: + return -EOPNOTSUPP; + } +} + +static irqreturn_t hdc3020_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct hdc3020_data *data; + s64 time; + int ret; + + data = iio_priv(indio_dev); + ret = hdc3020_read_be16(data, HDC3020_R_STATUS); + if (ret < 0) + return IRQ_HANDLED; + + if (!(ret & (HDC3020_STATUS_T_HIGH_ALERT | HDC3020_STATUS_T_LOW_ALERT | + HDC3020_STATUS_RH_HIGH_ALERT | HDC3020_STATUS_RH_LOW_ALERT))) + return IRQ_NONE; + + time = iio_get_time_ns(indio_dev); + if (ret & 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), + time); + + if (ret & 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), + time); + + if (ret & 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), + time); + + if (ret & 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), + time); + + 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) @@ -350,6 +580,23 @@ static int hdc3020_probe(struct i2c_client *client) indio_dev->info = &hdc3020_info; indio_dev->channels = hdc3020_channels; indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels); + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, hdc3020_interrupt_handler, + IRQF_ONESHOT, "hdc3020", + indio_dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to request IRQ\n"); + + /* + * The alert output is activated by default upon power up, + * hardware reset, and soft reset. Clear the status register. + */ + ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS); + if (ret) + return ret; + } ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0); if (ret)