// SPDX-License-Identifier: GPL-2.0+
/*
* hdc3020.c - Support for the TI HDC3020,HDC3021 and HDC3022
* temperature + relative humidity sensors
*
* Copyright (C) 2023
*
* Copyright (C) 2024 Liebherr-Electronics and Drives GmbH
*
* Datasheet: https://www.ti.com/lit/ds/symlink/hdc3020.pdf
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/crc8.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm.h>
#include <linux/regulator/consumer.h>
#include <linux/units.h>
#include <linux/unaligned.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#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_MICRO -
39872968
#define HDC3020_MAX_TEMP_MICRO
124875639
#define HDC3020_MAX_TEMP_HYST_MICRO
164748607
#define HDC3020_MAX_HUM_MICRO
99220264
struct hdc3020_data {
struct i2c_client *client;
struct gpio_desc *reset_gpio;
struct regulator *vdd_supply;
/*
* Ensure that the sensor configuration (currently only heater is
* supported) will not be changed during the process of reading
* sensor data (this driver will try HDC3020_READ_RETRY_TIMES times
* if the device does not respond).
*/
struct mutex lock;
};
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),
},
{
/*
* For setting the internal heater, which can be switched on to
* prevent or remove any condensation that may develop when the
* ambient environment approaches its dew point temperature.
*/
.type = IIO_CURRENT,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),
.output =
1,
},
};
DECLARE_CRC8_TABLE(hdc3020_crc8_table);
static int hdc3020_write_bytes(
struct hdc3020_data *data, u8 *buf, u8 len)
{
struct i2c_client *client = data->client;
struct i2c_msg msg;
int ret, cnt;
msg.addr = client->addr;
msg.flags =
0;
msg.buf = buf;
msg.len = len;
/*
* During the measurement process, HDC3020 will not return data.
* So wait for a while and try again
*/
for (cnt =
0; cnt < HDC3020_READ_RETRY_TIMES; cnt++) {
ret = i2c_transfer(client->adapter, &msg,
1);
if (ret ==
1)
return 0;
mdelay(HDC3020_BUSY_DELAY_MS);
}
dev_err(&client->dev,
"Could not write sensor command\n");
return -ETIMEDOUT;
}
static
int hdc3020_read_bytes(
struct hdc3020_data *data, u16 reg, u8 *buf,
int len)
{
u8 reg_buf[
2];
int ret, cnt;
struct i2c_client *client = data->client;
struct i2c_msg msg[
2] = {
[
0] = {
.addr = client->addr,
.flags =
0,
.buf = reg_buf,
.len =
2,
},
[
1] = {
.addr = client->addr,
.flags = I2C_M_RD,
.buf = buf,
.len = len,
},
};
put_unaligned_be16(reg, reg_buf);
/*
* During the measurement process, HDC3020 will not return data.
* So wait for a while and try again
*/
for (cnt =
0; cnt < HDC3020_READ_RETRY_TIMES; cnt++) {
ret = i2c_transfer(client->adapter, msg,
2);
if (ret ==
2)
return 0;
mdelay(HDC3020_BUSY_DELAY_MS);
}
dev_err(&client->dev,
"Could not read sensor data\n");
return -ETIMEDOUT;
}
static int hdc3020_read_be16(
struct hdc3020_data *data, u16 reg)
{
u8 crc, buf[
3];
int ret;
ret = hdc3020_read_bytes(data, reg, buf,
3);
if (ret <
0)
return ret;
crc = crc8(hdc3020_crc8_table, buf,
2, CRC8_INIT_VALUE);
if (crc != buf[
2])
return -EINVAL;
return get_unaligned_be16(buf);
}
static int hdc3020_exec_cmd(
struct hdc3020_data *data, u16 reg)
{
u8 reg_buf[
2];
put_unaligned_be16(reg, reg_buf);
return hdc3020_write_bytes(data, reg_buf,
2);
}
static int hdc3020_read_measurement(
struct hdc3020_data *data,
enum iio_chan_type type,
int *val)
{
u8 crc, buf[
6];
int ret;
ret = hdc3020_read_bytes(data, HDC3020_R_T_RH_AUTO, buf,
6);
if (ret <
0)
return ret;
/* CRC check of the temperature measurement */
crc = crc8(hdc3020_crc8_table, buf,
2, CRC8_INIT_VALUE);
if (crc != buf[
2])
return -EINVAL;
/* CRC check of the relative humidity measurement */
crc = crc8(hdc3020_crc8_table, buf +
3,
2, CRC8_INIT_VALUE);
if (crc != buf[
5])
return -EINVAL;
if (type == IIO_TEMP)
*val = get_unaligned_be16(buf);
else if (type == IIO_HUMIDITYRELATIVE)
*val = get_unaligned_be16(&buf[
3]);
else
return -EINVAL;
return 0;
}
static int hdc3020_read_raw(
struct iio_dev *indio_dev,
struct iio_chan_spec
const *chan,
int *val,
int *val2,
long mask)
{
struct hdc3020_data *data = iio_priv(indio_dev);
int ret;
if (chan->type != IIO_TEMP && chan->type != IIO_HUMIDITYRELATIVE)
return -EINVAL;
switch (mask) {
case IIO_CHAN_INFO_RAW: {
guard(mutex)(&data->lock);
ret = hdc3020_read_measurement(data, chan->type, val);
if (ret <
0)
return ret;
return IIO_VAL_INT;
}
case IIO_CHAN_INFO_PEAK: {
guard(mutex)(&data->lock);
if (chan->type == IIO_TEMP)
ret = hdc3020_read_be16(data, HDC3020_R_T_HIGH_AUTO);
else
ret = hdc3020_read_be16(data, HDC3020_R_RH_HIGH_AUTO);
if (ret <
0)
return ret;
*val = ret;
return IIO_VAL_INT;
}
case IIO_CHAN_INFO_TROUGH: {
guard(mutex)(&data->lock);
if (chan->type == IIO_TEMP)
ret = hdc3020_read_be16(data, HDC3020_R_T_LOW_AUTO);
else
ret = hdc3020_read_be16(data, HDC3020_R_RH_LOW_AUTO);
if (ret <
0)
return ret;
*val = ret;
return IIO_VAL_INT;
}
case IIO_CHAN_INFO_SCALE:
*val2 =
65536;
if (chan->type == IIO_TEMP)
*val =
175;
else
*val =
100;
return IIO_VAL_FRACTIONAL;
case IIO_CHAN_INFO_OFFSET:
if (chan->type != IIO_TEMP)
return -EINVAL;
*val = -
16852;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static int hdc3020_read_available(
struct iio_dev *indio_dev,
struct iio_chan_spec
const *chan,
const int **vals,
int *type,
int *length,
long mask)
{
if (mask != IIO_CHAN_INFO_RAW || chan->type != IIO_CURRENT)
return -EINVAL;
*vals = hdc3020_heater_vals;
*type = IIO_VAL_INT;
return IIO_AVAIL_RANGE;
}
static int hdc3020_update_heater(
struct hdc3020_data *data,
int val)
{
u8 buf[
5];
int ret;
if (val < hdc3020_heater_vals[
0] || val > hdc3020_heater_vals[
2])
return -EINVAL;
if (!val)
hdc3020_exec_cmd(data, HDC3020_HEATER_DISABLE);
put_unaligned_be16(HDC3020_HEATER_CONFIG, buf);
put_unaligned_be16(val & GENMASK(
13,
0), &buf[
2]);
buf[
4] = crc8(hdc3020_crc8_table, buf +
2,
2, CRC8_INIT_VALUE);
ret = hdc3020_write_bytes(data, buf,
5);
if (ret <
0)
return ret;
return hdc3020_exec_cmd(data, HDC3020_HEATER_ENABLE);
}
static int hdc3020_write_raw(
struct iio_dev *indio_dev,
struct iio_chan_spec
const *chan,
int val,
int val2,
long mask)
{
struct hdc3020_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (chan->type != IIO_CURRENT)
return -EINVAL;
guard(mutex)(&data->lock);
return hdc3020_update_heater(data, val);
}
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,
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, reg_val, reg_thresh_rd, reg_clr_rd, reg_thresh_wr, reg_clr_wr;
s64 s_thresh, s_hyst, s_clr;
int s_val, thresh, clr, ret;
/* Select threshold registers */
if (dir == IIO_EV_DIR_RISING) {
reg_thresh_rd = HDC3020_R_T_RH_THRESH_HIGH;
reg_thresh_wr = HDC3020_S_T_RH_THRESH_HIGH;
reg_clr_rd = HDC3020_R_T_RH_THRESH_HIGH_CLR;
reg_clr_wr = HDC3020_S_T_RH_THRESH_HIGH_CLR;
}
else {
reg_thresh_rd = HDC3020_R_T_RH_THRESH_LOW;
reg_thresh_wr = HDC3020_S_T_RH_THRESH_LOW;
reg_clr_rd = HDC3020_R_T_RH_THRESH_LOW_CLR;
reg_clr_wr = HDC3020_S_T_RH_THRESH_LOW_CLR;
}
guard(mutex)(&data->lock);
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)
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) {
case IIO_TEMP:
switch (info) {
case IIO_EV_INFO_VALUE:
s_val = max(s_val, HDC3020_MIN_TEMP_MICRO);
s_val = min(s_val, HDC3020_MAX_TEMP_MICRO);
reg = reg_thresh_wr;
reg_val = hdc3020_thresh_set_temp(s_val, thresh);
ret = _hdc3020_write_thresh(data, reg, reg_val);
if (ret <
0)
return ret;
/* 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;
case IIO_HUMIDITYRELATIVE:
s_val = (s_val <
0) ?
0 : min(s_val, HDC3020_MAX_HUM_MICRO);
switch (info) {
case IIO_EV_INFO_VALUE:
reg = reg_thresh_wr;
reg_val = hdc3020_thresh_set_hum(s_val, thresh);
ret = _hdc3020_write_thresh(data, reg, reg_val);
if (ret <
0)
return 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;
default:
return -EOPNOTSUPP;
}
return _hdc3020_write_thresh(data, reg, reg_val);
}
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_thresh, reg_clr;
int thresh, clr, ret;
/* Select threshold registers */
if (dir == IIO_EV_DIR_RISING) {
reg_thresh = HDC3020_R_T_RH_THRESH_HIGH;
reg_clr = HDC3020_R_T_RH_THRESH_HIGH_CLR;
}
else {
reg_thresh = HDC3020_R_T_RH_THRESH_LOW;
reg_clr = HDC3020_R_T_RH_THRESH_LOW_CLR;
}
guard(mutex)(&data->lock);
ret = hdc3020_read_be16(data, reg_thresh);
if (ret <
0)
return ret;
switch (chan->type) {
case IIO_TEMP:
thresh = hdc3020_thresh_get_temp(ret);
switch (info) {
case IIO_EV_INFO_VALUE:
*val = thresh;
break;
case IIO_EV_INFO_HYSTERESIS:
ret = hdc3020_read_be16(data, reg_clr);
if (ret <
0)
return ret;
clr = hdc3020_thresh_get_temp(ret);
*val = abs(thresh - clr);
break;
default:
return -EOPNOTSUPP;
}
*val2 =
65535;
return IIO_VAL_FRACTIONAL;
case IIO_HUMIDITYRELATIVE:
thresh = hdc3020_thresh_get_hum(ret);
switch (info) {
case IIO_EV_INFO_VALUE:
*val = thresh;
break;
case IIO_EV_INFO_HYSTERESIS:
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;
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 int hdc3020_power_off(
struct hdc3020_data *data)
{
hdc3020_exec_cmd(data, HDC3020_EXIT_AUTO);
if (data->reset_gpio)
gpiod_set_value_cansleep(data->reset_gpio,
1);
return regulator_disable(data->vdd_supply);
}
static int hdc3020_power_on(
struct hdc3020_data *data)
{
int ret;
ret = regulator_enable(data->vdd_supply);
if (ret)
return ret;
fsleep(
5000);
if (data->reset_gpio) {
gpiod_set_value_cansleep(data->reset_gpio,
0);
fsleep(
3000);
}
if (data->client->irq) {
/*
* 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) {
hdc3020_power_off(data);
return ret;
}
}
ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0);
if (ret)
hdc3020_power_off(data);
return ret;
}
static void hdc3020_exit(
void *data)
{
hdc3020_power_off(data);
}
static int hdc3020_probe(
struct i2c_client *client)
{
struct iio_dev *indio_dev;
struct hdc3020_data *data;
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;
indio_dev = devm_iio_device_alloc(&client->dev,
sizeof(*data));
if (!indio_dev)
return -ENOMEM;
dev_set_drvdata(&client->dev, indio_dev);
data = iio_priv(indio_dev);
data->client = client;
mutex_init(&data->lock);
crc8_populate_msb(hdc3020_crc8_table, HDC3020_CRC8_POLYNOMIAL);
indio_dev->name =
"hdc3020";
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &hdc3020_info;
indio_dev->channels = hdc3020_channels;
indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels);
data->vdd_supply = devm_regulator_get(&client->dev,
"vdd");
if (IS_ERR(data->vdd_supply))
return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply),
"Unable to get VDD regulator\n");
data->reset_gpio = devm_gpiod_get_optional(&client->dev,
"reset",
GPIOD_OUT_HIGH);
if (IS_ERR(data->reset_gpio))
return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio),
"Cannot get reset GPIO\n");
ret = hdc3020_power_on(data);
if (ret)
return dev_err_probe(&client->dev, ret,
"Power on failed\n");
ret = devm_add_action_or_reset(&data->client->dev, hdc3020_exit, data);
if (ret)
return ret;
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");
}
ret = devm_iio_device_register(&data->client->dev, indio_dev);
if (ret)
return dev_err_probe(&client->dev, ret,
"Failed to add device");
return 0;
}
static int hdc3020_suspend(
struct device *dev)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct hdc3020_data *data = iio_priv(iio_dev);
return hdc3020_power_off(data);
}
static int hdc3020_resume(
struct device *dev)
{
struct iio_dev *iio_dev = dev_get_drvdata(dev);
struct hdc3020_data *data = iio_priv(iio_dev);
return hdc3020_power_on(data);
}
static DEFINE_SIMPLE_DEV_PM_OPS(hdc3020_pm_ops, hdc3020_suspend, hdc3020_resume);
static const struct i2c_device_id hdc3020_id[] = {
{
"hdc3020" },
{
"hdc3021" },
{
"hdc3022" },
{ }
};
MODULE_DEVICE_TABLE(i2c, hdc3020_id);
static const struct of_device_id hdc3020_dt_ids[] = {
{ .compatible =
"ti,hdc3020" },
{ .compatible =
"ti,hdc3021" },
{ .compatible =
"ti,hdc3022" },
{ }
};
MODULE_DEVICE_TABLE(of, hdc3020_dt_ids);
static struct i2c_driver hdc3020_driver = {
.driver = {
.name =
"hdc3020",
.pm = pm_sleep_ptr(&hdc3020_pm_ops),
.of_match_table = hdc3020_dt_ids,
},
.probe = hdc3020_probe,
.id_table = hdc3020_id,
};
module_i2c_driver(hdc3020_driver);
MODULE_AUTHOR(
"Javier Carrasco <javier.carrasco.cruz@gmail.com>");
MODULE_AUTHOR(
"Li peiyu <579lpy@gmail.com>");
MODULE_DESCRIPTION(
"TI HDC3020 humidity and temperature sensor driver");
MODULE_LICENSE(
"GPL");