1

iio: humidity: Add support for ENS210

Add support for ENS210/ENS210A/ENS211/ENS212/ENS213A/ENS215.

The ENS21x is a family of temperature and relative humidity sensors with
accuracies tailored to the needs of specific applications.

Signed-off-by: Joshua Felmeden <jfelmeden@thegoodpenguin.co.uk>
Link: https://patch.msgid.link/20240805-ens21x-v6-2-5bb576ef26a6@thegoodpenguin.co.uk
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
Joshua Felmeden 2024-08-05 09:12:53 +01:00 committed by Jonathan Cameron
parent 6dde94264a
commit c524fbca67
3 changed files with 351 additions and 0 deletions

View File

@ -25,6 +25,17 @@ config DHT11
Other sensors should work as well as long as they speak the Other sensors should work as well as long as they speak the
same protocol. same protocol.
config ENS210
tristate "ENS210 temperature and humidity sensor"
depends on I2C
select CRC7
help
Say yes here to get support for the ScioSense ENS210 family of
humidity and temperature sensors.
This driver can also be built as a module. If so, the module will be
called ens210.
config HDC100X config HDC100X
tristate "TI HDC100x relative humidity and temperature sensor" tristate "TI HDC100x relative humidity and temperature sensor"
depends on I2C depends on I2C

View File

@ -5,6 +5,7 @@
obj-$(CONFIG_AM2315) += am2315.o obj-$(CONFIG_AM2315) += am2315.o
obj-$(CONFIG_DHT11) += dht11.o obj-$(CONFIG_DHT11) += dht11.o
obj-$(CONFIG_ENS210) += ens210.o
obj-$(CONFIG_HDC100X) += hdc100x.o obj-$(CONFIG_HDC100X) += hdc100x.o
obj-$(CONFIG_HDC2010) += hdc2010.o obj-$(CONFIG_HDC2010) += hdc2010.o
obj-$(CONFIG_HDC3020) += hdc3020.o obj-$(CONFIG_HDC3020) += hdc3020.o

View File

@ -0,0 +1,339 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ens210.c - Support for ScioSense ens210 temperature & humidity sensor family
*
* (7-bit I2C slave address 0x43 ENS210)
* (7-bit I2C slave address 0x43 ENS210A)
* (7-bit I2C slave address 0x44 ENS211)
* (7-bit I2C slave address 0x45 ENS212)
* (7-bit I2C slave address 0x46 ENS213A)
* (7-bit I2C slave address 0x47 ENS215)
*
* Datasheet:
* https://www.sciosense.com/wp-content/uploads/2024/04/ENS21x-Datasheet.pdf
* https://www.sciosense.com/wp-content/uploads/2023/12/ENS210-Datasheet.pdf
*/
#include <linux/crc7.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/types.h>
#include <asm/unaligned.h>
/* register definitions */
#define ENS210_REG_PART_ID 0x00
#define ENS210_REG_DIE_REV 0x02
#define ENS210_REG_UID 0x04
#define ENS210_REG_SYS_CTRL 0x10
#define ENS210_REG_SYS_STAT 0x11
#define ENS210_REG_SENS_RUN 0x21
#define ENS210_REG_SENS_START 0x22
#define ENS210_REG_SENS_STOP 0x23
#define ENS210_REG_SENS_STAT 0x24
#define ENS210_REG_T_VAL 0x30
#define ENS210_REG_H_VAL 0x33
/* value definitions */
#define ENS210_SENS_START_T_START BIT(0)
#define ENS210_SENS_START_H_START BIT(1)
#define ENS210_SENS_STAT_T_ACTIVE BIT(0)
#define ENS210_SENS_STAT_H_ACTIVE BIT(1)
#define ENS210_SYS_CTRL_LOW_POWER_ENABLE BIT(0)
#define ENS210_SYS_CTRL_SYS_RESET BIT(7)
#define ENS210_SYS_STAT_SYS_ACTIVE BIT(0)
enum ens210_partnumber {
ENS210 = 0x0210,
ENS210A = 0xa210,
ENS211 = 0x0211,
ENS212 = 0x0212,
ENS213A = 0xa213,
ENS215 = 0x0215,
};
/**
* struct ens210_chip_info - Humidity/Temperature chip specific information
* @name: name of device
* @part_id: chip identifier
* @conv_time_msec: time for conversion calculation in m/s
*/
struct ens210_chip_info {
const char *name;
enum ens210_partnumber part_id;
unsigned int conv_time_msec;
};
/**
* struct ens210_data - Humidity/Temperature sensor device structure
* @client: i2c client
* @chip_info: chip specific information
* @lock: lock protecting against simultaneous callers of get_measurement
* since multiple uninterrupted transactions are required
*/
struct ens210_data {
struct i2c_client *client;
const struct ens210_chip_info *chip_info;
struct mutex lock;
};
/* calculate 17-bit crc7 */
static u8 ens210_crc7(u32 val)
{
unsigned int val_be = (val & 0x1ffff) >> 0x8;
return crc7_be(0xde, (u8 *)&val_be, 3) >> 1;
}
static int ens210_get_measurement(struct iio_dev *indio_dev, bool temp, int *val)
{
struct ens210_data *data = iio_priv(indio_dev);
struct device *dev = &data->client->dev;
u32 regval;
u8 regval_le[3];
int ret;
/* assert read */
ret = i2c_smbus_write_byte_data(data->client, ENS210_REG_SENS_START,
temp ? ENS210_SENS_START_T_START :
ENS210_SENS_START_H_START);
if (ret)
return ret;
/* wait for conversion to be ready */
msleep(data->chip_info->conv_time_msec);
ret = i2c_smbus_read_byte_data(data->client, ENS210_REG_SENS_STAT);
if (ret < 0)
return ret;
/* perform read */
ret = i2c_smbus_read_i2c_block_data(
data->client, temp ? ENS210_REG_T_VAL : ENS210_REG_H_VAL, 3,
regval_le);
if (ret < 0) {
dev_err(dev, "failed to read register");
return -EIO;
}
if (ret != 3) {
dev_err(dev, "expected 3 bytes, received %d\n", ret);
return -EIO;
}
regval = get_unaligned_le24(regval_le);
if (ens210_crc7(regval) != ((regval >> 17) & 0x7f)) {
dev_err(dev, "invalid crc\n");
return -EIO;
}
if (!((regval >> 16) & 0x1)) {
dev_err(dev, "data is not valid");
return -EIO;
}
*val = regval & GENMASK(15, 0);
return IIO_VAL_INT;
}
static int ens210_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *channel, int *val,
int *val2, long mask)
{
struct ens210_data *data = iio_priv(indio_dev);
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
scoped_guard(mutex, &data->lock) {
ret = ens210_get_measurement(
indio_dev, channel->type == IIO_TEMP, val);
if (ret)
return ret;
return IIO_VAL_INT;
}
return -EINVAL; /* compiler warning workaround */
case IIO_CHAN_INFO_SCALE:
if (channel->type == IIO_TEMP) {
*val = 15;
*val2 = 625000;
} else {
*val = 1;
*val2 = 953125;
}
return IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_OFFSET:
*val = -17481;
*val2 = 600000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_chan_spec ens210_channels[] = {
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE) |
BIT(IIO_CHAN_INFO_OFFSET),
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
}
};
static const struct iio_info ens210_info = {
.read_raw = ens210_read_raw,
};
static int ens210_probe(struct i2c_client *client)
{
struct ens210_data *data;
struct iio_dev *indio_dev;
uint16_t part_id;
int ret;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
I2C_FUNC_SMBUS_WRITE_BYTE |
I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
return dev_err_probe(&client->dev, -EOPNOTSUPP,
"adapter does not support some i2c transactions\n");
}
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);
data->chip_info = i2c_get_match_data(client);
ret = devm_regulator_get_enable(&client->dev, "vdd");
if (ret)
return ret;
/* reset device */
ret = i2c_smbus_write_byte_data(client, ENS210_REG_SYS_CTRL,
ENS210_SYS_CTRL_SYS_RESET);
if (ret)
return ret;
/* wait for device to become active */
usleep_range(4000, 5000);
/* disable low power mode */
ret = i2c_smbus_write_byte_data(client, ENS210_REG_SYS_CTRL, 0x00);
if (ret)
return ret;
/* wait for device to finish */
usleep_range(4000, 5000);
/* get part_id */
ret = i2c_smbus_read_word_data(client, ENS210_REG_PART_ID);
if (ret < 0)
return ret;
part_id = ret;
if (part_id != data->chip_info->part_id) {
dev_info(&client->dev,
"Part ID does not match (0x%04x != 0x%04x)\n", part_id,
data->chip_info->part_id);
}
/* reenable low power */
ret = i2c_smbus_write_byte_data(client, ENS210_REG_SYS_CTRL,
ENS210_SYS_CTRL_LOW_POWER_ENABLE);
if (ret)
return ret;
indio_dev->name = data->chip_info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ens210_channels;
indio_dev->num_channels = ARRAY_SIZE(ens210_channels);
indio_dev->info = &ens210_info;
return devm_iio_device_register(&client->dev, indio_dev);
}
static const struct ens210_chip_info ens210_chip_info_data = {
.name = "ens210",
.part_id = ENS210,
.conv_time_msec = 130,
};
static const struct ens210_chip_info ens210a_chip_info_data = {
.name = "ens210a",
.part_id = ENS210A,
.conv_time_msec = 130,
};
static const struct ens210_chip_info ens211_chip_info_data = {
.name = "ens211",
.part_id = ENS211,
.conv_time_msec = 32,
};
static const struct ens210_chip_info ens212_chip_info_data = {
.name = "ens212",
.part_id = ENS212,
.conv_time_msec = 32,
};
static const struct ens210_chip_info ens213a_chip_info_data = {
.name = "ens213a",
.part_id = ENS213A,
.conv_time_msec = 130,
};
static const struct ens210_chip_info ens215_chip_info_data = {
.name = "ens215",
.part_id = ENS215,
.conv_time_msec = 130,
};
static const struct of_device_id ens210_of_match[] = {
{ .compatible = "sciosense,ens210", .data = &ens210_chip_info_data },
{ .compatible = "sciosense,ens210a", .data = &ens210a_chip_info_data },
{ .compatible = "sciosense,ens211", .data = &ens211_chip_info_data },
{ .compatible = "sciosense,ens212", .data = &ens212_chip_info_data },
{ .compatible = "sciosense,ens213a", .data = &ens213a_chip_info_data },
{ .compatible = "sciosense,ens215", .data = &ens215_chip_info_data },
{ }
};
MODULE_DEVICE_TABLE(of, ens210_of_match);
static const struct i2c_device_id ens210_id_table[] = {
{ "ens210", (kernel_ulong_t)&ens210_chip_info_data },
{ "ens210a", (kernel_ulong_t)&ens210a_chip_info_data },
{ "ens211", (kernel_ulong_t)&ens211_chip_info_data },
{ "ens212", (kernel_ulong_t)&ens212_chip_info_data },
{ "ens213a", (kernel_ulong_t)&ens213a_chip_info_data },
{ "ens215", (kernel_ulong_t)&ens215_chip_info_data },
{ }
};
MODULE_DEVICE_TABLE(i2c, ens210_id_table);
static struct i2c_driver ens210_driver = {
.probe = ens210_probe,
.id_table = ens210_id_table,
.driver = {
.name = "ens210",
.of_match_table = ens210_of_match,
},
};
module_i2c_driver(ens210_driver);
MODULE_DESCRIPTION("ScioSense ENS210 temperature and humidity sensor driver");
MODULE_AUTHOR("Joshua Felmeden <jfelmeden@thegoodpenguin.co.uk>");
MODULE_LICENSE("GPL");