[PATCH v3 1/2] iio: humidity: Add driver for ti HDC302x humidity sensors

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Li peiyu <579lpy@xxxxxxxxx>

Add support for HDC302x integrated capacitive based relative
humidity (RH) and temperature sensor.
This driver supports reading values, reading the maximum and
minimum of values and controlling the integrated heater of
the sensor.

Signed-off-by: Li peiyu <579lpy@xxxxxxxxx>
---
changes for v3:
- Removed the custom ABI
- Give up calculating values in the driver
- Use read_avail callback to get available parameters
- Changed the scope of the lock to make the code more concise
- Fixed the code format issue
changes for v2:
- Added static modification to global variables
- change the methord to read peak value

 drivers/iio/humidity/Kconfig   |  11 +
 drivers/iio/humidity/Makefile  |   1 +
 drivers/iio/humidity/hdc3020.c | 522 +++++++++++++++++++++++++++++++++
 3 files changed, 534 insertions(+)
 create mode 100644 drivers/iio/humidity/hdc3020.c

diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig
index 2de5494e7c22..7b585bc7db9a 100644
--- a/drivers/iio/humidity/Kconfig
+++ b/drivers/iio/humidity/Kconfig
@@ -48,6 +48,17 @@ config HDC2010
 	  To compile this driver as a module, choose M here: the module
 	  will be called hdc2010.
 
+config HDC3020
+	tristate "TI HDC3020 relative humidity and temperature sensor"
+	depends on I2C
+	help
+	  Say yes here to build support for the Texas Instruments
+	  HDC3020,HDC3021 and HDC3022 relative humidity and temperature
+	  sensors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called hdc3020.
+
 config HID_SENSOR_HUMIDITY
 	tristate "HID Environmental humidity sensor"
 	depends on HID_SENSOR_HUB
diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile
index f19ff3de97c5..5fbeef299f61 100644
--- a/drivers/iio/humidity/Makefile
+++ b/drivers/iio/humidity/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_AM2315) += am2315.o
 obj-$(CONFIG_DHT11) += dht11.o
 obj-$(CONFIG_HDC100X) += hdc100x.o
 obj-$(CONFIG_HDC2010) += hdc2010.o
+obj-$(CONFIG_HDC3020) += hdc3020.o
 obj-$(CONFIG_HID_SENSOR_HUMIDITY) += hid-sensor-humidity.o
 
 hts221-y := hts221_core.o \
diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c
new file mode 100644
index 000000000000..bbd90abfb7df
--- /dev/null
+++ b/drivers/iio/humidity/hdc3020.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hdc3020.c - Support for the TI HDC3020,HDC3021 and HDC3022
+ * temperature + relative humidity sensors
+ *
+ * Copyright (C) 2023
+ *
+ * Datasheet: https://www.ti.com/lit/ds/symlink/hdc3020.pdf
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+
+#include <asm/unaligned.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define READ_RETRY_TIMES 10
+#define BUSY_DELAY 10
+
+static const u8 HDC3020_S_AUTO_10HZ_MOD0[2] = { 0x27, 0x37 };
+
+static const u8 HDC3020_EXIT_AUTO[2] = { 0x30, 0x93 };
+
+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_ENABLE_HEATER[2] = { 0x30, 0x6D };
+static const u8 HDC3020_DISABLE_HEATER[2] = { 0x30, 0x66 };
+
+static const u8 HDC3020_HEATER_FULL[5] = { 0x30, 0x6E, 0x3F, 0xFF, 0x06 };
+static const u8 HDC3020_HEATER_HALF[5] = { 0x30, 0x6E, 0x03, 0xFF, 0x00 };
+static const u8 HDC3020_HEATER_QUARTER[5] = { 0x30, 0x6E, 0x00, 0x9F, 0x96 };
+
+struct hdc3020_data {
+	struct i2c_client *client;
+	struct mutex lock;
+
+	int temp;
+	int humidity;
+	int temp_high_peak;
+	int temp_low_peak;
+	int humidity_high_peak;
+	int humidity_low_peak;
+};
+
+enum hdc3020_heater_power {
+	HEATER_OFF = 0,
+	HEATER_QUARTER = 1,
+	HEATER_HALF = 2,
+	HEATER_FULL = 3,
+};
+
+static const int HEATER_VALUES[] = { HEATER_OFF, HEATER_QUARTER, HEATER_HALF,
+				     HEATER_FULL };
+
+static const struct iio_chan_spec hdc3020_channels[] = {
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+		BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		/* For minimum value during measurement */
+		.type = IIO_TEMP,
+		.channel = 0,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PEAK) |
+		BIT(IIO_CHAN_INFO_PEAK_SCALE),
+		.indexed = 1,
+	},
+	{
+		/* For maxmum value during measurement */
+		.type = IIO_TEMP,
+		.channel = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PEAK) |
+		BIT(IIO_CHAN_INFO_PEAK_SCALE),
+		.indexed = 1,
+	},
+	{
+		.type = IIO_HUMIDITYRELATIVE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+		BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		/* For minimum value during measurement */
+		.type = IIO_HUMIDITYRELATIVE,
+		.channel = 0,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PEAK) |
+		BIT(IIO_CHAN_INFO_PEAK_SCALE),
+		.indexed = 1,
+	},
+	{
+		/* For maxmum value during measurement */
+		.type = IIO_HUMIDITYRELATIVE,
+		.channel = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PEAK) |
+		BIT(IIO_CHAN_INFO_PEAK_SCALE),
+		.indexed = 1,
+	},
+	{
+		/* 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 = 1,
+		.output = 1,
+	},
+};
+
+static int hdc3020_write_bytes(struct hdc3020_data *data, const u8 *buf,
+			       u8 len)
+{
+	int ret, cnt;
+	struct i2c_msg msg;
+	struct i2c_client *client = data->client;
+
+	msg.addr = client->addr;
+	msg.flags = 0;
+	msg.buf = (char *)buf;
+	msg.len = len;
+
+	/*
+	 * During the measurement process, HDC3020 will not return data.
+	 * So wait for a while and try again
+	 */
+	ret = 0;
+	cnt = 0;
+
+	while (cnt < READ_RETRY_TIMES) {
+		ret = i2c_transfer(client->adapter, &msg, 1);
+		if (ret == 1) {
+			ret = 0;
+			break;
+		}
+
+		mdelay(BUSY_DELAY);
+		cnt++;
+	}
+	if (cnt == READ_RETRY_TIMES) {
+		dev_err(&client->dev, "Could not write sensor command\n");
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+static int hdc3020_read_bytes(struct hdc3020_data *data, const u8 *buf,
+			      void *val, int len)
+{
+	int ret, cnt;
+	struct i2c_client *client = data->client;
+	struct i2c_msg msg[2] = {
+		[0] = {
+			.addr = client->addr,
+			.flags = 0,
+			.buf = (char *)buf,
+			.len = 2,
+		},
+		[1] = {
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.buf = val,
+			.len = len,
+		},
+	};
+
+	/*
+	 * During the measurement process, HDC3020 will not return data.
+	 * So wait for a while and try again
+	 */
+	ret = 0;
+	cnt = 0;
+
+	while (cnt < READ_RETRY_TIMES) {
+		ret = i2c_transfer(client->adapter, msg, 2);
+		if (ret == 2) {
+			ret = 0;
+			break;
+		}
+		mdelay(BUSY_DELAY);
+		cnt++;
+	}
+
+	if (cnt == READ_RETRY_TIMES) {
+		dev_err(&client->dev, "Could not read sensor data\n");
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+static int hdc3020_read_measurement(struct hdc3020_data *data)
+{
+	int ret;
+	u8 buf[6];
+
+	ret = hdc3020_read_bytes(data, HDC3020_R_T_RH_AUTO, (void *)buf, 6);
+	if (ret < 0)
+		return ret;
+	/* Subtract offset: 45 / 175 * 65535 = 16852 */
+	data->temp = (int)get_unaligned_be16(buf) - 16852;
+	data->humidity = get_unaligned_be16(&buf[3]);
+	return 0;
+}
+
+/*
+ * After exiting the automatic measurement mode or resetting, the peak
+ * value will be reset to the default value
+ * This methord is used to get the lowest temp measured during automatic
+ * measurement
+ */
+static int hdc3020_read_high_peak_t(struct hdc3020_data *data)
+{
+	int ret;
+	u8 buf[3];
+
+	ret = hdc3020_read_bytes(data, HDC3020_R_T_HIGH_AUTO, (void *)buf, 3);
+	if (ret < 0)
+		return ret;
+	/* Subtract offset: 45 / 175 * 65535 = 16852 */
+	data->temp_high_peak = (int)get_unaligned_be16(buf) - 16852;
+
+	return 0;
+}
+
+/*
+ * This methord is used to get the highest temp measured during automatic
+ * measurement
+ */
+static int hdc3020_read_low_peak_t(struct hdc3020_data *data)
+{
+	int ret;
+	u8 buf[3];
+
+	ret = hdc3020_read_bytes(data, HDC3020_R_T_LOW_AUTO, (void *)buf, 3);
+	if (ret < 0)
+		return ret;
+	/* Subtract offset: 45 / 175 * 65535 = 16852 */
+	data->temp_low_peak = (int)get_unaligned_be16(buf) - 16852;
+
+	return 0;
+}
+
+/*
+ * This methord is used to get the highest humidity measured during automatic
+ * measurement
+ */
+static int hdc3020_read_high_peak_rh(struct hdc3020_data *data)
+{
+	int ret;
+	u8 buf[3];
+
+	ret = hdc3020_read_bytes(data, HDC3020_R_RH_HIGH_AUTO, (void *)buf, 3);
+	if (ret < 0)
+		return ret;
+	data->humidity_high_peak = get_unaligned_be16(buf);
+
+	return 0;
+}
+
+/*
+ * This methord is used to get the lowest humidity measured during automatic
+ * measurement
+ */
+static int hdc3020_read_low_peak_rh(struct hdc3020_data *data)
+{
+	int ret;
+	u8 buf[3];
+
+	ret = hdc3020_read_bytes(data, HDC3020_R_RH_LOW_AUTO, (void *)buf, 3);
+	if (ret < 0)
+		return ret;
+	data->humidity_low_peak = get_unaligned_be16(buf);
+
+	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 = 0;
+
+	mutex_lock(&data->lock);
+	switch (mask) {
+
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret < 0)
+			break;
+
+		ret = hdc3020_read_measurement(data);
+		if (ret < 0)
+			break;
+
+		if (chan->type == IIO_TEMP)
+			*val = data->temp;
+		else if (chan->type == IIO_HUMIDITYRELATIVE)
+			*val = data->humidity;
+		iio_device_release_direct_mode(indio_dev);
+		ret = IIO_VAL_INT;
+		break;
+
+	case IIO_CHAN_INFO_PEAK:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret < 0)
+			break;
+
+		if (chan->type == IIO_TEMP) {
+			if (chan->channel == 0) {
+				ret = hdc3020_read_low_peak_t(data);
+				if (ret < 0)
+					break;
+
+				*val = data->temp_low_peak;
+			} else if (chan->channel == 1) {
+				ret = hdc3020_read_high_peak_t(data);
+				if (ret < 0)
+					break;
+
+				*val = data->temp_high_peak;
+			}
+		} else if (chan->type == IIO_HUMIDITYRELATIVE) {
+			if (chan->channel == 0) {
+				ret = hdc3020_read_low_peak_rh(data);
+				if (ret < 0)
+					break;
+
+				*val = data->humidity_low_peak;
+			} else if (chan->channel == 1) {
+				ret = hdc3020_read_high_peak_rh(data);
+				if (ret < 0)
+					break;
+
+				*val = data->humidity_high_peak;
+			}
+		}
+		iio_device_release_direct_mode(indio_dev);
+		ret = IIO_VAL_INT;
+		break;
+
+	case IIO_CHAN_INFO_SCALE:
+	case IIO_CHAN_INFO_PEAK_SCALE:
+		*val2 = 65536;
+		if (chan->type == IIO_TEMP)
+			*val = 175;
+		else
+			*val = 100;
+		ret = IIO_VAL_FRACTIONAL;
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+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 = HEATER_VALUES;
+	*type = IIO_VAL_INT;
+	*length = 4;
+
+	return IIO_AVAIL_LIST;
+}
+
+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);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type != IIO_CURRENT || val2 != 0 || val < 0
+		    || val > 3)
+			return -EINVAL;
+
+		mutex_lock(&data->lock);
+		switch (val) {
+		case HEATER_QUARTER:
+			ret =
+			    hdc3020_write_bytes(data, HDC3020_HEATER_QUARTER,
+						5);
+			if (ret < 0)
+				break;
+
+			ret =
+			    hdc3020_write_bytes(data, HDC3020_ENABLE_HEATER, 2);
+			break;
+		case HEATER_HALF:
+			ret = hdc3020_write_bytes(data, HDC3020_HEATER_HALF, 5);
+			if (ret < 0)
+				break;
+
+			ret =
+			    hdc3020_write_bytes(data, HDC3020_ENABLE_HEATER, 2);
+			break;
+		case HEATER_FULL:
+			ret = hdc3020_write_bytes(data, HDC3020_HEATER_FULL, 5);
+			if (ret < 0)
+				break;
+
+			ret =
+			    hdc3020_write_bytes(data, HDC3020_ENABLE_HEATER, 2);
+			break;
+		case HEATER_OFF:
+			ret =
+			    hdc3020_write_bytes(data, HDC3020_DISABLE_HEATER,
+						2);
+			break;
+		}
+		mutex_unlock(&data->lock);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct iio_info hdc3020_info = {
+	.read_raw = hdc3020_read_raw,
+	.write_raw = hdc3020_write_raw,
+	.read_avail = hdc3020_read_available,
+};
+
+static void hdc3020_stop(void *data)
+{
+	hdc3020_write_bytes((struct hdc3020_data *)data, HDC3020_EXIT_AUTO, 2);
+}
+
+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;
+
+	data = iio_priv(indio_dev);
+	data->client = client;
+	mutex_init(&data->lock);
+
+	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);
+
+	ret = hdc3020_write_bytes(data, HDC3020_S_AUTO_10HZ_MOD0, 2);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Unable to set up measurement\n");
+
+	ret = devm_add_action_or_reset(&data->client->dev, hdc3020_stop, data);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to add device\n\n");
+
+	ret = devm_iio_device_register(&data->client->dev, indio_dev);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to add device\n\n");
+
+	return 0;
+}
+
+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",
+		.of_match_table = hdc3020_dt_ids,
+	},
+	.probe = hdc3020_probe,
+	.id_table = hdc3020_id,
+};
+
+module_i2c_driver(hdc3020_driver);
+
+MODULE_AUTHOR("Li peiyu <579lpy@xxxxxxxxx>");
+MODULE_DESCRIPTION("TI HDC3020 humidity and temperature sensor driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1





[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux