diff mbox series

[v3,2/2] iio: humidity: Add support for ENS21x

Message ID 20240710-ens21x-v3-2-4e3fbcf2a7fb@thegoodpenguin.co.uk (mailing list archive)
State Changes Requested
Headers show
Series iio: humidity: Add support for en21x sensor family | expand

Commit Message

Joshua Felmeden July 10, 2024, 1:24 p.m. UTC
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>
---
 drivers/iio/humidity/Kconfig  |  11 ++
 drivers/iio/humidity/Makefile |   1 +
 drivers/iio/humidity/ens21x.c | 346 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 358 insertions(+)

Comments

kernel test robot July 12, 2024, 11:50 a.m. UTC | #1
Hi Joshua,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 1ebab783647a9e3bf357002d5c4ff060c8474a0a]

url:    https://github.com/intel-lab-lkp/linux/commits/Joshua-Felmeden/dt-bindings-iio-humidity-add-ENS21x-sensor-family/20240711-022826
base:   1ebab783647a9e3bf357002d5c4ff060c8474a0a
patch link:    https://lore.kernel.org/r/20240710-ens21x-v3-2-4e3fbcf2a7fb%40thegoodpenguin.co.uk
patch subject: [PATCH v3 2/2] iio: humidity: Add support for ENS21x
config: loongarch-randconfig-r122-20240712 (https://download.01.org/0day-ci/archive/20240712/202407121921.LHla7p7i-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 14.1.0
reproduce: (https://download.01.org/0day-ci/archive/20240712/202407121921.LHla7p7i-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202407121921.LHla7p7i-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> drivers/iio/humidity/ens21x.c:84:23: sparse: sparse: restricted __be32 degrades to integer
>> drivers/iio/humidity/ens21x.c:143:26: sparse: sparse: cast to restricted __le32
>> drivers/iio/humidity/ens21x.c:283:19: sparse: sparse: cast to restricted __le16

vim +84 drivers/iio/humidity/ens21x.c

    80	
    81	/* calculate 17-bit crc7 */
    82	static u8 ens21x_crc7(u32 val)
    83	{
  > 84		u32 val_be = (htonl(val & 0x1ffff) >> 0x8);
    85	
    86		return crc7_be(0xde, (u8 *)&val_be, 3) >> 1;
    87	}
    88	
    89	static int ens21x_get_measurement(struct iio_dev *indio_dev, bool temp, int *val)
    90	{
    91		u32 regval, regval_le;
    92		int ret, tries;
    93		struct ens21x_dev *dev_data = iio_priv(indio_dev);
    94	
    95		/* assert read */
    96		i2c_smbus_write_byte_data(dev_data->client, ENS21X_REG_SENS_START,
    97					  temp ? ENS21X_SENS_START_T_START :
    98						 ENS21X_SENS_START_H_START);
    99	
   100		/* wait for conversion to be ready */
   101		switch (dev_data->part_id) {
   102		case ENS210:
   103		case ENS210A:
   104			msleep(ENS210_CONST_CONVERSION_TIME);
   105			break;
   106		case ENS211:
   107		case ENS212:
   108			msleep(ENS212_CONST_CONVERSION_TIME);
   109			break;
   110		case ENS213A:
   111		case ENS215:
   112			msleep(ENS215_CONST_CONVERSION_TIME);
   113			break;
   114		default:
   115			dev_err(&dev_data->client->dev, "unrecognised device");
   116			return -ENODEV;
   117		}
   118	
   119		tries = 10;
   120		while (tries-- > 0) {
   121			usleep_range(4000, 5000);
   122			ret = i2c_smbus_read_byte_data(dev_data->client,
   123						       ENS21X_REG_SENS_STAT);
   124			if (ret < 0)
   125				continue;
   126			if (!(ret & (temp ? ENS21X_SENS_STAT_T_ACTIVE :
   127					    ENS21X_SENS_STAT_H_ACTIVE)))
   128				break;
   129		}
   130		if (tries < 0) {
   131			dev_err(&indio_dev->dev, "timeout waiting for sensor reading\n");
   132			return -EIO;
   133		}
   134	
   135		/* perform read */
   136		ret = i2c_smbus_read_i2c_block_data(
   137			dev_data->client, temp ? ENS21X_REG_T_VAL : ENS21X_REG_H_VAL, 3,
   138			(u8 *)&regval_le);
   139		if (ret < 0) {
   140			dev_err(&dev_data->client->dev, "failed to read register");
   141			return -EIO;
   142		} else if (ret == 3) {
 > 143			regval = le32_to_cpu(regval_le);
   144			if (ens21x_crc7(regval) == ((regval >> 17) & 0x7f)) {
   145				*val = regval & 0xffff;
   146				return IIO_VAL_INT;
   147			}
   148			/* crc fail */
   149			dev_err(&indio_dev->dev, "ens invalid crc\n");
   150			return -EIO;
   151		}
   152	
   153		dev_err(&indio_dev->dev, "expected 3 bytes, received %d\n", ret);
   154		return -EIO;
   155	}
   156	
   157	static int ens21x_read_raw(struct iio_dev *indio_dev,
   158				   struct iio_chan_spec const *channel, int *val,
   159				   int *val2, long mask)
   160	{
   161		struct ens21x_dev *dev_data = iio_priv(indio_dev);
   162		int ret = -EINVAL;
   163	
   164		switch (mask) {
   165		case IIO_CHAN_INFO_RAW:
   166			mutex_lock(&dev_data->lock);
   167			ret = ens21x_get_measurement(
   168				indio_dev, channel->type == IIO_TEMP, val);
   169			mutex_unlock(&dev_data->lock);
   170			break;
   171		case IIO_CHAN_INFO_SCALE:
   172			if (channel->type == IIO_TEMP) {
   173				*val = ENS21X_CONST_TEMP_SCALE_INT;
   174				*val2 = ENS21X_CONST_TEMP_SCALE_DEC;
   175			} else {
   176				*val = ENS21X_CONST_HUM_SCALE_INT;
   177				*val2 = ENS21X_CONST_HUM_SCALE_DEC;
   178			}
   179			ret = IIO_VAL_INT_PLUS_MICRO;
   180			break;
   181		case IIO_CHAN_INFO_OFFSET:
   182			if (channel->type == IIO_TEMP) {
   183				*val = ENS21X_CONST_TEMP_OFFSET_INT;
   184				*val2 = ENS21X_CONST_TEMP_OFFSET_DEC;
   185				ret = IIO_VAL_INT_PLUS_MICRO;
   186				break;
   187			}
   188			*val = 0;
   189			ret =  IIO_VAL_INT;
   190			break;
   191		default:
   192			break;
   193		}
   194		return ret;
   195	}
   196	
   197	static const struct iio_chan_spec ens21x_channels[] = {
   198		/* Temperature channel */
   199		{
   200			.type = IIO_TEMP,
   201			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
   202					      BIT(IIO_CHAN_INFO_SCALE) |
   203					      BIT(IIO_CHAN_INFO_OFFSET),
   204		},
   205		/* Humidity channel */
   206		{
   207			.type = IIO_HUMIDITYRELATIVE,
   208			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
   209					      BIT(IIO_CHAN_INFO_SCALE) |
   210					      BIT(IIO_CHAN_INFO_OFFSET),
   211		}
   212	};
   213	
   214	static const struct iio_info ens21x_info = {
   215		.read_raw = ens21x_read_raw,
   216	};
   217	
   218	static int ens21x_probe(struct i2c_client *client)
   219	{
   220		const struct i2c_device_id *id = i2c_client_get_device_id(client);
   221		const struct of_device_id *match;
   222		struct ens21x_dev *dev_data;
   223		struct iio_dev *indio_dev;
   224		uint16_t part_id_le, part_id;
   225		int ret, tries;
   226	
   227		if (!i2c_check_functionality(client->adapter,
   228				I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
   229				I2C_FUNC_SMBUS_WRITE_BYTE |
   230				I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
   231			dev_err(&client->dev,
   232				"adapter does not support some i2c transactions\n");
   233			return -EOPNOTSUPP;
   234		}
   235	
   236		match = i2c_of_match_device(ens21x_of_match, client);
   237		if (!match)
   238			return -ENODEV;
   239	
   240		indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data));
   241		if (!indio_dev)
   242			return -ENOMEM;
   243	
   244		dev_data = iio_priv(indio_dev);
   245		i2c_set_clientdata(client, indio_dev);
   246		dev_data->client = client;
   247		mutex_init(&dev_data->lock);
   248	
   249		/* reset device */
   250		ret = i2c_smbus_write_byte_data(client, ENS21X_REG_SYS_CTRL,
   251						ENS21X_SYS_CTRL_SYS_RESET);
   252		if (ret)
   253			return ret;
   254	
   255		/* wait for device to become active */
   256		usleep_range(4000, 5000);
   257	
   258		/* disable low power mode */
   259		ret = i2c_smbus_write_byte_data(client, ENS21X_REG_SYS_CTRL, 0x00);
   260		if (ret)
   261			return ret;
   262	
   263		/* wait for device to become active */
   264		tries = 10;
   265		while (tries-- > 0) {
   266			msleep(20);
   267			ret = i2c_smbus_read_byte_data(client, ENS21X_REG_SYS_STAT);
   268			if (ret < 0)
   269				return ret;
   270			if (ret & ENS21X_SYS_STAT_SYS_ACTIVE)
   271				break;
   272		}
   273		if (tries < 0) {
   274			dev_err(&client->dev,
   275				"timeout waiting for ens21x to become active\n");
   276			return -EIO;
   277		}
   278	
   279		/* get part_id */
   280		part_id_le = i2c_smbus_read_word_data(client, ENS21X_REG_PART_ID);
   281		if (part_id_le < 0)
   282			return part_id_le;
 > 283		part_id = le16_to_cpu(part_id_le);
   284	
   285		if (part_id != id->driver_data) {
   286			dev_err(&client->dev,
   287				"Part ID does not match (0x%04x != 0x%04lx)\n", part_id,
   288				id->driver_data);
   289			return -ENODEV;
   290		}
   291	
   292		/* reenable low power */
   293		ret = i2c_smbus_write_byte_data(client, ENS21X_REG_SYS_CTRL,
   294						ENS21X_SYS_CTRL_LOW_POWER_ENABLE);
   295		if (ret)
   296			return ret;
   297	
   298		dev_data->part_id = part_id;
   299	
   300		indio_dev->name = id->name;
   301		indio_dev->modes = INDIO_DIRECT_MODE;
   302		indio_dev->channels = ens21x_channels;
   303		indio_dev->num_channels = ARRAY_SIZE(ens21x_channels);
   304		indio_dev->info = &ens21x_info;
   305	
   306		return devm_iio_device_register(&client->dev, indio_dev);
   307	}
   308
kernel test robot July 13, 2024, 7:42 a.m. UTC | #2
Hi Joshua,

kernel test robot noticed the following build errors:

[auto build test ERROR on 1ebab783647a9e3bf357002d5c4ff060c8474a0a]

url:    https://github.com/intel-lab-lkp/linux/commits/Joshua-Felmeden/dt-bindings-iio-humidity-add-ENS21x-sensor-family/20240711-022826
base:   1ebab783647a9e3bf357002d5c4ff060c8474a0a
patch link:    https://lore.kernel.org/r/20240710-ens21x-v3-2-4e3fbcf2a7fb%40thegoodpenguin.co.uk
patch subject: [PATCH v3 2/2] iio: humidity: Add support for ENS21x
config: sparc64-randconfig-r053-20240712 (https://download.01.org/0day-ci/archive/20240713/202407131554.tOb1HnRA-lkp@intel.com/config)
compiler: sparc64-linux-gcc (GCC) 14.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240713/202407131554.tOb1HnRA-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202407131554.tOb1HnRA-lkp@intel.com/

All errors (new ones prefixed by >>):

   sparc64-linux-ld: drivers/iio/humidity/ens21x.o: in function `ens21x_get_measurement':
>> ens21x.c:(.text+0x388): undefined reference to `crc7_be'
Christophe JAILLET July 13, 2024, 10:44 a.m. UTC | #3
Le 10/07/2024 à 15:24, Joshua Felmeden a écrit :
> 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-tUaQ5FxYRYX4aQPF92CzsNBc4/FLrbF6@public.gmane.org>
> ---
>   drivers/iio/humidity/Kconfig  |  11 ++
>   drivers/iio/humidity/Makefile |   1 +
>   drivers/iio/humidity/ens21x.c | 346 ++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 358 insertions(+)

Hi,

as kernel test robot complained, there will be a v4.

So here are a few nitpicks/questions, in case it helps.

...

> +#include <linux/types.h>
> +#include <linux/i2c.h>
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/crc7.h>

Nitpick: usually, it is prefered to keep #include alphabetically ordered.

...

> +
> +/* magic constants */
> +#define ENS21X_CONST_TEMP_SCALE_INT 15 /* integer part of temperature scale (1/64) */
> +#define ENS21X_CONST_TEMP_SCALE_DEC 625000 /* decimal part of temperature scale */
> +#define ENS21X_CONST_HUM_SCALE_INT 1 /* integer part of humidity scale (1/512) */
> +#define ENS21X_CONST_HUM_SCALE_DEC 953125 /* decimal part of humidity scale */
> +#define ENS21X_CONST_TEMP_OFFSET_INT -17481 /* temperature offset (64 * -273.15) */
> +#define ENS21X_CONST_TEMP_OFFSET_DEC 600000 /* decimal part of offset */
> +#define ENS210_CONST_CONVERSION_TIME 130
> +#define ENS212_CONST_CONVERSION_TIME 32
> +#define ENS215_CONST_CONVERSION_TIME 132

Datasheet says 130 for ENS213A and ENS215.
Is it a typo?
If 132 is intentional, maybe a samll comment explaining why would be 
welcomed?

...

> +static int ens21x_get_measurement(struct iio_dev *indio_dev, bool temp, int *val)
> +{
> +	u32 regval, regval_le;
> +	int ret, tries;
> +	struct ens21x_dev *dev_data = iio_priv(indio_dev);
> +
> +	/* assert read */
> +	i2c_smbus_write_byte_data(dev_data->client, ENS21X_REG_SENS_START,
> +				  temp ? ENS21X_SENS_START_T_START :
> +					 ENS21X_SENS_START_H_START);
> +
> +	/* wait for conversion to be ready */
> +	switch (dev_data->part_id) {
> +	case ENS210:
> +	case ENS210A:
> +		msleep(ENS210_CONST_CONVERSION_TIME);
> +		break;
> +	case ENS211:
> +	case ENS212:
> +		msleep(ENS212_CONST_CONVERSION_TIME);
> +		break;
> +	case ENS213A:
> +	case ENS215:
> +		msleep(ENS215_CONST_CONVERSION_TIME);
> +		break;
> +	default:
> +		dev_err(&dev_data->client->dev, "unrecognised device");
> +		return -ENODEV;
> +	}
> +
> +	tries = 10;
> +	while (tries-- > 0) {
> +		usleep_range(4000, 5000);

We just msleep()'ed the max expected time for the conversion. So, maybe 
the code could be re-arranged so that this delay is done only if we retry?

> +		ret = i2c_smbus_read_byte_data(dev_data->client,
> +					       ENS21X_REG_SENS_STAT);
> +		if (ret < 0)
> +			continue;
> +		if (!(ret & (temp ? ENS21X_SENS_STAT_T_ACTIVE :
> +				    ENS21X_SENS_STAT_H_ACTIVE)))
> +			break;
> +	}
> +	if (tries < 0) {
> +		dev_err(&indio_dev->dev, "timeout waiting for sensor reading\n");
> +		return -EIO;
> +	}

...

> +	indio_dev->name = id->name;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = ens21x_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(ens21x_channels);
> +	indio_dev->info = &ens21x_info;
> +
> +	return devm_iio_device_register(&client->dev, indio_dev);
> +}
> +
> +

Nitpick: unneeded 2nd new line.

> +static const struct of_device_id ens21x_of_match[] = {
> +	{ .compatible = "sciosense,ens210", .data = (void *)ENS210},
> +	{ .compatible = "sciosense,ens210a", .data = (void *)ENS210A },
> +	{ .compatible = "sciosense,ens211", .data = (void *)ENS211},

...

CJ
Jonathan Cameron July 13, 2024, 11:47 a.m. UTC | #4
On Wed, 10 Jul 2024 14:24:05 +0100
Joshua Felmeden <jfelmeden@thegoodpenguin.co.uk> wrote:

> 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>
Hi Joshua,

Various comments inline

Jonathan

> ---
>  drivers/iio/humidity/Kconfig  |  11 ++
>  drivers/iio/humidity/Makefile |   1 +
>  drivers/iio/humidity/ens21x.c | 346 ++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 358 insertions(+)
> 
> diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig
> index b15b7a3b66d5..ff62abf730d1 100644
> --- a/drivers/iio/humidity/Kconfig
> +++ b/drivers/iio/humidity/Kconfig
> @@ -25,6 +25,17 @@ config DHT11
>  	  Other sensors should work as well as long as they speak the
>  	  same protocol.
>  
> +config ENS21X
> +	tristate "ENS21X temperature and humidity sensor"
> +	depends on I2C
> +	help
> +	  Say yes here to get support for the ScioSense ENS21X family of
> +	  humidity and temperature sensors.
> +
> +	  This driver can also be built as a module. If so, the module will be
> +	  called ens21x.
> +

Keep to local style, so one blank line only.

> +
>  config HDC100X
>  	tristate "TI HDC100x relative humidity and temperature sensor"
>  	depends on I2C

> diff --git a/drivers/iio/humidity/ens21x.c b/drivers/iio/humidity/ens21x.c
> new file mode 100644
> index 000000000000..7b2e279d1559
> --- /dev/null
> +++ b/drivers/iio/humidity/ens21x.c
> @@ -0,0 +1,346 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * ens21x.c - Support for ScioSense ens21x
> + *           temperature & humidity sensor

Very short line wrap so odd looking formatting.

> + *
> + * (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/types.h>
> +#include <linux/i2c.h>
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>

Fix the various of stuff below and there won't be anything from either of these
headers but you should include
mod_devicetable.h for the various id tables.

> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>

Not seeing this used. It's for custom attributes only.

> +#include <linux/crc7.h>
> +
> +/* register definitions */
> +#define ENS21X_REG_PART_ID		0x00
> +#define ENS21X_REG_DIE_REV		0x02
> +#define ENS21X_REG_UID			0x04
> +#define ENS21X_REG_SYS_CTRL		0x10
> +#define ENS21X_REG_SYS_STAT		0x11
> +#define ENS21X_REG_SENS_RUN		0x21
> +#define ENS21X_REG_SENS_START		0x22
> +#define ENS21X_REG_SENS_STOP		0x23
> +#define ENS21X_REG_SENS_STAT		0x24
> +#define ENS21X_REG_T_VAL		0x30
> +#define ENS21X_REG_H_VAL		0x33
> +
> +/* value definitions */
> +#define ENS21X_SENS_START_T_START		BIT(0)
> +#define ENS21X_SENS_START_H_START		BIT(1)
> +
> +#define ENS21X_SENS_STAT_T_ACTIVE		BIT(0)
> +#define ENS21X_SENS_STAT_H_ACTIVE		BIT(1)
> +
> +#define ENS21X_SYS_CTRL_LOW_POWER_ENABLE	BIT(0)
> +#define ENS21X_SYS_CTRL_SYS_RESET		BIT(7)
> +
> +#define ENS21X_SYS_STAT_SYS_ACTIVE		BIT(0)
> +
> +/* magic constants */
> +#define ENS21X_CONST_TEMP_SCALE_INT 15 /* integer part of temperature scale (1/64) */
> +#define ENS21X_CONST_TEMP_SCALE_DEC 625000 /* decimal part of temperature scale */

These defines aren't magic in any sense! They are the actual values.
As such, just use them inline in the code where they will also
be self documenting (so probably no need for comments unless they are
relating them to maths used to find the values).


> +#define ENS21X_CONST_HUM_SCALE_INT 1 /* integer part of humidity scale (1/512) */
> +#define ENS21X_CONST_HUM_SCALE_DEC 953125 /* decimal part of humidity scale */
> +#define ENS21X_CONST_TEMP_OFFSET_INT -17481 /* temperature offset (64 * -273.15) */
> +#define ENS21X_CONST_TEMP_OFFSET_DEC 600000 /* decimal part of offset */
> +#define ENS210_CONST_CONVERSION_TIME 130

Conversion times are more reasonable things to have defines for if they
turn up in multiple places.  Good if the define tells us the units
though and no need for it to involve CONST.  Anything in capitals is assumed
to be a define.
ENS210_CONVERSION_TIME_MSECS etc


> +#define ENS212_CONST_CONVERSION_TIME 32
> +#define ENS215_CONST_CONVERSION_TIME 132
> +
> +static const struct of_device_id ens21x_of_match[];

This won't be needed after changes suggested below.

> +
> +struct ens21x_dev {
> +	struct i2c_client *client;
> +	struct mutex lock;
All locks need documentation of what data they are protecting.

> +	int part_id;

I'd expect to see this embedded in the chip_info structure suggested below.

> +};
> +
> +enum ens21x_partnumber {
> +	ENS210	= 0x0210,
> +	ENS210A	= 0xa210,
> +	ENS211	= 0x0211,
> +	ENS212	= 0x0212,
> +	ENS213A	= 0xa213,
> +	ENS215	= 0x0215,
> +};
> +
> +/* calculate 17-bit crc7 */
> +static u8 ens21x_crc7(u32 val)
> +{
> +	u32 val_be = (htonl(val & 0x1ffff) >> 0x8);
Long time since I've seen htonl in kernel code.

	__be32 val_be = cpu_to_be32(val);

I'm suspicious though. You take a le24 below, convert
that to CPU then to be24. That's just always byte
swapping.  If you need to flip the byte order, just do
that.


> +
> +	return crc7_be(0xde, (u8 *)&val_be, 3) >> 1;
> +}
> +
> +static int ens21x_get_measurement(struct iio_dev *indio_dev, bool temp, int *val)
> +{
> +	u32 regval, regval_le;
> +	int ret, tries;
> +	struct ens21x_dev *dev_data = iio_priv(indio_dev);

dev_data tends to suggest output dev_get_drvdata() or similar.
More common in IIO to name it after the part so ens210_data
or similar.


> +
> +	/* assert read */
> +	i2c_smbus_write_byte_data(dev_data->client, ENS21X_REG_SENS_START,
> +				  temp ? ENS21X_SENS_START_T_START :
> +					 ENS21X_SENS_START_H_START);

Check return values from all i2c calls.  If it's expected to return an
error (that happens for some corner cases) then document why we ignore it.


> +
> +	/* wait for conversion to be ready */
> +	switch (dev_data->part_id) {

This per chip type data belongs in a chip_info structure (see below)
so that this just ebcomes

	msleep(dev_data->chip_info.conv_time_msec);

or something like that. It is almost always preferable to make this
sort of stuff picking between data, rather than inline code as
it puts all the per device stuff in one place rather than scattered
throughout.


> +	case ENS210:
> +	case ENS210A:
> +		msleep(ENS210_CONST_CONVERSION_TIME);
> +		break;
> +	case ENS211:
> +	case ENS212:
> +		msleep(ENS212_CONST_CONVERSION_TIME);
> +		break;
> +	case ENS213A:
> +	case ENS215:
> +		msleep(ENS215_CONST_CONVERSION_TIME);
> +		break;
> +	default:
> +		dev_err(&dev_data->client->dev, "unrecognised device");
> +		return -ENODEV;
> +	}
> +
> +	tries = 10;
> +	while (tries-- > 0) {

A retry loop like this needs a comment on why it's needed and
why this particular number of retries.

> +		usleep_range(4000, 5000);
> +		ret = i2c_smbus_read_byte_data(dev_data->client,
> +					       ENS21X_REG_SENS_STAT);
> +		if (ret < 0)
> +			continue;
Error expected here?  That's nasty if true that the device stops
talking i2c right when capturing data.  If not, return an error,
not carry on, as it's a sign the comms is broken.

> +		if (!(ret & (temp ? ENS21X_SENS_STAT_T_ACTIVE :
> +				    ENS21X_SENS_STAT_H_ACTIVE)))
> +			break;
> +	}
> +	if (tries < 0) {
> +		dev_err(&indio_dev->dev, "timeout waiting for sensor reading\n");
> +		return -EIO;
> +	}
> +
> +	/* perform read */
> +	ret = i2c_smbus_read_i2c_block_data(
> +		dev_data->client, temp ? ENS21X_REG_T_VAL : ENS21X_REG_H_VAL, 3,
> +		(u8 *)&regval_le);

If it is 3 bytes, read into a u8[3] and use get_unaligned_le24()

> +	if (ret < 0) {
> +		dev_err(&dev_data->client->dev, "failed to read register");
> +		return -EIO;
> +	} else if (ret == 3) {
> +		regval = le32_to_cpu(regval_le);
> +		if (ens21x_crc7(regval) == ((regval >> 17) & 0x7f)) {

pull that bytes from the u8[3] mentioned above

> +			*val = regval & 0xffff;
> +			return IIO_VAL_INT;
Always prefer the good path to be the inline one for ease of code reading..
So flip the logic.
Generally we keep the IIO return values to the boundary code with
the IIO core, so I'd return 0 here and check for errors at the claller
before returning IIO_VAL_INT.  That way we don't need to check all callers
of this function handling the unusual return code right.

> +		}get_measurement
> +		/* crc fail */
> +		dev_err(&indio_dev->dev, "ens invalid crc\n");
> +		return -EIO;
> +	}
> +
> +	dev_err(&indio_dev->dev, "expected 3 bytes, received %d\n", ret);
Check that before the crc.  As then you can reduce indent of the more
complex code.

> +	return -EIO;
> +}
> +
> +static int ens21x_read_raw(struct iio_dev *indio_dev,
> +			   struct iio_chan_spec const *channel, int *val,
> +			   int *val2, long mask)
> +{
> +	struct ens21x_dev *dev_data = iio_priv(indio_dev);
> +	int ret = -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		mutex_lock(&dev_data->lock);

		scoped_guard(mutex)(&dev_data->lock) {
			ret = ens21x_get_measurement();
			if (ret)
				return ret;

			return IIO_VAL_INT;

		}	

> +		ret = ens21x_get_measurement(
> +			indio_dev, channel->type == IIO_TEMP, val);
> +		mutex_unlock(&dev_data->lock);
> +		break;
> +	case IIO_CHAN_INFO_SCALE:
> +		if (channel->type == IIO_TEMP) {
> +			*val = ENS21X_CONST_TEMP_SCALE_INT;
> +			*val2 = ENS21X_CONST_TEMP_SCALE_DEC;
> +		} else {
> +			*val = ENS21X_CONST_HUM_SCALE_INT;
> +			*val2 = ENS21X_CONST_HUM_SCALE_DEC;
> +		}
> +		ret = IIO_VAL_INT_PLUS_MICRO;
		return IIO_VAL_INT_PLUS_MICRO;

> +		break;
> +	case IIO_CHAN_INFO_OFFSET:
> +		if (channel->type == IIO_TEMP) {
> +			*val = ENS21X_CONST_TEMP_OFFSET_INT;
> +			*val2 = ENS21X_CONST_TEMP_OFFSET_DEC;
> +			ret = IIO_VAL_INT_PLUS_MICRO;
> +			break;
> +		}
> +		*val = 0;
> +		ret =  IIO_VAL_INT;
Odd spacing + 
		return IIO_VAL_INT;
> +		break;
> +	default:
> +		break;
		return -EINVAL;
> +	}
> +	return ret;
get rid of this as after changes above this is unreachable.

For code readability reasons, early returns are good.
Someone interested in a given path has to look at the minimal amount
of code rather than having to chase through to a much later return
with nothing actually done on the way there.

> +}
> +
> +static const struct iio_chan_spec ens21x_channels[] = {
> +	/* Temperature channel */

Obvious from .type, so I'd drop the comments.

> +	{
> +		.type = IIO_TEMP,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE) |
> +				      BIT(IIO_CHAN_INFO_OFFSET),
> +	},
> +	/* Humidity channel */
> +	{
> +		.type = IIO_HUMIDITYRELATIVE,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE) |
> +				      BIT(IIO_CHAN_INFO_OFFSET),
> +	}
> +};
> +
> +static const struct iio_info ens21x_info = {
> +	.read_raw = ens21x_read_raw,
> +};
> +
> +static int ens21x_probe(struct i2c_client *client)
> +{
> +	const struct i2c_device_id *id = i2c_client_get_device_id(client);
> +	const struct of_device_id *match;
> +	struct ens21x_dev *dev_data;
> +	struct iio_dev *indio_dev;
> +	uint16_t part_id_le, part_id;
> +	int ret, tries;
> +
> +	if (!i2c_check_functionality(client->adapter,
> +			I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
> +			I2C_FUNC_SMBUS_WRITE_BYTE |
> +			I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
> +		dev_err(&client->dev,
> +			"adapter does not support some i2c transactions\n");
> +		return -EOPNOTSUPP;
in probe, nicer to use dev_err_probe() throughout. Doesn't matter much in this
case, but good for consistency with where it might.

		return dev_err_probe(&client->dev, -EOPNOTSUPP,
				     "adapter does not...
> +	}
> +
> +	match = i2c_of_match_device(ens21x_of_match, client);
> +	if (!match)
> +		return -ENODEV;
Why?

I suspect what you actually want is i2c_get_match_data(client);
That gets the data for all supported firmware types including DT.



> +
> +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	dev_data = iio_priv(indio_dev);
> +	i2c_set_clientdata(client, indio_dev);
> +	dev_data->client = client;
> +	mutex_init(&dev_data->lock);
> +
turn the power on?
	ret = devm_regulator_get_enabled() or similar

> +	/* reset device */
> +	ret = i2c_smbus_write_byte_data(client, ENS21X_REG_SYS_CTRL,
> +					ENS21X_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, ENS21X_REG_SYS_CTRL, 0x00);
> +	if (ret)
> +		return ret;
> +
> +	/* wait for device to become active */
Why this long?  Reference some power up delay in the datahseet?
> +	tries = 10;
> +	while (tries-- > 0) {
> +		msleep(20);
> +		ret = i2c_smbus_read_byte_data(client, ENS21X_REG_SYS_STAT);
> +		if (ret < 0)
> +			return ret;
> +		if (ret & ENS21X_SYS_STAT_SYS_ACTIVE)
> +			break;
> +	}
> +	if (tries < 0) {
> +		dev_err(&client->dev,
> +			"timeout waiting for ens21x to become active\n");
> +		return -EIO;
> +	}
> +
> +	/* get part_id */
> +	part_id_le = i2c_smbus_read_word_data(client, ENS21X_REG_PART_ID);


> +	if (part_id_le < 0)
> +		return part_id_le;
> +	part_id = le16_to_cpu(part_id_le);

Smbus has a byte order and the values returned from i2c_smbus_read_word_data()
are already cpu endian.  So this shouldn't be needed.
(that's the reason we have i2c_smbus_read_word_swapped() for when it is in
reverse order of the smbus spec - happens annoyingly commonly).

> +
> +	if (part_id != id->driver_data) {
> +		dev_err(&client->dev,
> +			"Part ID does not match (0x%04x != 0x%04lx)\n", part_id,

dev_info() and no error return only.
This hard check is something we used to do, but DT maintainers pointed out a while
back that it breaks the use of fallback compatibles to allow old drivers to work
out of the box with new hardware.   It is useful to print a message though saying
we've seen something unexpected.

> +			id->driver_data);
> +		return -ENODEV;
> +	}
> +
> +	/* reenable low power */
> +	ret = i2c_smbus_write_byte_data(client, ENS21X_REG_SYS_CTRL,
> +					ENS21X_SYS_CTRL_LOW_POWER_ENABLE);
> +	if (ret)
> +		return ret;
> +
> +	dev_data->part_id = part_id;
> +
> +	indio_dev->name = id->name;

This tends to be fragile as we add other firmware types etc.  So
I'd prefer to see the name inside the chip_info structure suggested below.

> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = ens21x_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(ens21x_channels);
> +	indio_dev->info = &ens21x_info;
> +
> +	return devm_iio_device_register(&client->dev, indio_dev);
> +}
> +
> +
> +static const struct of_device_id ens21x_of_match[] = {
> +	{ .compatible = "sciosense,ens210", .data = (void *)ENS210},

Putting enums into the data fields has a habit of causing bugs.
You've avoided the most common one which is a 0 value, but still
I'd prefer these to be a pointer to a ens210_chip_info structure.

> +	{ .compatible = "sciosense,ens210a", .data = (void *)ENS210A },
> +	{ .compatible = "sciosense,ens211", .data = (void *)ENS211},
> +	{ .compatible = "sciosense,ens212", .data = (void *)ENS212},
> +	{ .compatible = "sciosense,ens213a", .data = (void *)ENS213A },
> +	{ .compatible = "sciosense,ens215", .data = (void *)ENS215},
> +	{},
No comma after a 'terminator' like this as we don't want it to be easy
for people to put something here.

> +};
> +MODULE_DEVICE_TABLE(of, ens21x_of_match);
> +
> +static const struct i2c_device_id ens21x_id[] = {
> +	{"ens210", ENS210},
> +	{"ens210a", ENS210A},
> +	{"ens211", ENS211},
> +	{"ens212", ENS212},
> +	{"ens213a", ENS213A},
> +	{"ens215", ENS215},
> +	{}
Consistent spacing needed so after { and before } in all cases like htis.

> +};
> +MODULE_DEVICE_TABLE(i2c, ens21x_id);
> +
> +static struct i2c_driver ens21x_driver = {
> +	.probe = ens21x_probe,
> +	.id_table = ens21x_id,
> +	.driver = {
> +		.name = "ens21x",
> +		.of_match_table = ens21x_of_match,
> +	},
> +};
> +
> +module_i2c_driver(ens21x_driver);
> +
> +MODULE_DESCRIPTION("ScioSense ENS21x temperature and humidity sensor driver");
> +MODULE_AUTHOR("Joshua Felmeden <jfelmeden@thegoodpenguin.co.uk>");
> +MODULE_LICENSE("GPL");
> +

Unless I'm reading the diff wrong looks like a spare blank line at the end.
Trivial but delete that.

>
diff mbox series

Patch

diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig
index b15b7a3b66d5..ff62abf730d1 100644
--- a/drivers/iio/humidity/Kconfig
+++ b/drivers/iio/humidity/Kconfig
@@ -25,6 +25,17 @@  config DHT11
 	  Other sensors should work as well as long as they speak the
 	  same protocol.
 
+config ENS21X
+	tristate "ENS21X temperature and humidity sensor"
+	depends on I2C
+	help
+	  Say yes here to get support for the ScioSense ENS21X family of
+	  humidity and temperature sensors.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called ens21x.
+
+
 config HDC100X
 	tristate "TI HDC100x relative humidity and temperature sensor"
 	depends on I2C
diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile
index 5fbeef299f61..26590d06d11f 100644
--- a/drivers/iio/humidity/Makefile
+++ b/drivers/iio/humidity/Makefile
@@ -5,6 +5,7 @@ 
 
 obj-$(CONFIG_AM2315) += am2315.o
 obj-$(CONFIG_DHT11) += dht11.o
+obj-$(CONFIG_ENS21X) += ens21x.o
 obj-$(CONFIG_HDC100X) += hdc100x.o
 obj-$(CONFIG_HDC2010) += hdc2010.o
 obj-$(CONFIG_HDC3020) += hdc3020.o
diff --git a/drivers/iio/humidity/ens21x.c b/drivers/iio/humidity/ens21x.c
new file mode 100644
index 000000000000..7b2e279d1559
--- /dev/null
+++ b/drivers/iio/humidity/ens21x.c
@@ -0,0 +1,346 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ens21x.c - Support for ScioSense ens21x
+ *           temperature & humidity sensor
+ *
+ * (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/types.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/crc7.h>
+
+/* register definitions */
+#define ENS21X_REG_PART_ID		0x00
+#define ENS21X_REG_DIE_REV		0x02
+#define ENS21X_REG_UID			0x04
+#define ENS21X_REG_SYS_CTRL		0x10
+#define ENS21X_REG_SYS_STAT		0x11
+#define ENS21X_REG_SENS_RUN		0x21
+#define ENS21X_REG_SENS_START		0x22
+#define ENS21X_REG_SENS_STOP		0x23
+#define ENS21X_REG_SENS_STAT		0x24
+#define ENS21X_REG_T_VAL		0x30
+#define ENS21X_REG_H_VAL		0x33
+
+/* value definitions */
+#define ENS21X_SENS_START_T_START		BIT(0)
+#define ENS21X_SENS_START_H_START		BIT(1)
+
+#define ENS21X_SENS_STAT_T_ACTIVE		BIT(0)
+#define ENS21X_SENS_STAT_H_ACTIVE		BIT(1)
+
+#define ENS21X_SYS_CTRL_LOW_POWER_ENABLE	BIT(0)
+#define ENS21X_SYS_CTRL_SYS_RESET		BIT(7)
+
+#define ENS21X_SYS_STAT_SYS_ACTIVE		BIT(0)
+
+/* magic constants */
+#define ENS21X_CONST_TEMP_SCALE_INT 15 /* integer part of temperature scale (1/64) */
+#define ENS21X_CONST_TEMP_SCALE_DEC 625000 /* decimal part of temperature scale */
+#define ENS21X_CONST_HUM_SCALE_INT 1 /* integer part of humidity scale (1/512) */
+#define ENS21X_CONST_HUM_SCALE_DEC 953125 /* decimal part of humidity scale */
+#define ENS21X_CONST_TEMP_OFFSET_INT -17481 /* temperature offset (64 * -273.15) */
+#define ENS21X_CONST_TEMP_OFFSET_DEC 600000 /* decimal part of offset */
+#define ENS210_CONST_CONVERSION_TIME 130
+#define ENS212_CONST_CONVERSION_TIME 32
+#define ENS215_CONST_CONVERSION_TIME 132
+
+static const struct of_device_id ens21x_of_match[];
+
+struct ens21x_dev {
+	struct i2c_client *client;
+	struct mutex lock;
+	int part_id;
+};
+
+enum ens21x_partnumber {
+	ENS210	= 0x0210,
+	ENS210A	= 0xa210,
+	ENS211	= 0x0211,
+	ENS212	= 0x0212,
+	ENS213A	= 0xa213,
+	ENS215	= 0x0215,
+};
+
+/* calculate 17-bit crc7 */
+static u8 ens21x_crc7(u32 val)
+{
+	u32 val_be = (htonl(val & 0x1ffff) >> 0x8);
+
+	return crc7_be(0xde, (u8 *)&val_be, 3) >> 1;
+}
+
+static int ens21x_get_measurement(struct iio_dev *indio_dev, bool temp, int *val)
+{
+	u32 regval, regval_le;
+	int ret, tries;
+	struct ens21x_dev *dev_data = iio_priv(indio_dev);
+
+	/* assert read */
+	i2c_smbus_write_byte_data(dev_data->client, ENS21X_REG_SENS_START,
+				  temp ? ENS21X_SENS_START_T_START :
+					 ENS21X_SENS_START_H_START);
+
+	/* wait for conversion to be ready */
+	switch (dev_data->part_id) {
+	case ENS210:
+	case ENS210A:
+		msleep(ENS210_CONST_CONVERSION_TIME);
+		break;
+	case ENS211:
+	case ENS212:
+		msleep(ENS212_CONST_CONVERSION_TIME);
+		break;
+	case ENS213A:
+	case ENS215:
+		msleep(ENS215_CONST_CONVERSION_TIME);
+		break;
+	default:
+		dev_err(&dev_data->client->dev, "unrecognised device");
+		return -ENODEV;
+	}
+
+	tries = 10;
+	while (tries-- > 0) {
+		usleep_range(4000, 5000);
+		ret = i2c_smbus_read_byte_data(dev_data->client,
+					       ENS21X_REG_SENS_STAT);
+		if (ret < 0)
+			continue;
+		if (!(ret & (temp ? ENS21X_SENS_STAT_T_ACTIVE :
+				    ENS21X_SENS_STAT_H_ACTIVE)))
+			break;
+	}
+	if (tries < 0) {
+		dev_err(&indio_dev->dev, "timeout waiting for sensor reading\n");
+		return -EIO;
+	}
+
+	/* perform read */
+	ret = i2c_smbus_read_i2c_block_data(
+		dev_data->client, temp ? ENS21X_REG_T_VAL : ENS21X_REG_H_VAL, 3,
+		(u8 *)&regval_le);
+	if (ret < 0) {
+		dev_err(&dev_data->client->dev, "failed to read register");
+		return -EIO;
+	} else if (ret == 3) {
+		regval = le32_to_cpu(regval_le);
+		if (ens21x_crc7(regval) == ((regval >> 17) & 0x7f)) {
+			*val = regval & 0xffff;
+			return IIO_VAL_INT;
+		}
+		/* crc fail */
+		dev_err(&indio_dev->dev, "ens invalid crc\n");
+		return -EIO;
+	}
+
+	dev_err(&indio_dev->dev, "expected 3 bytes, received %d\n", ret);
+	return -EIO;
+}
+
+static int ens21x_read_raw(struct iio_dev *indio_dev,
+			   struct iio_chan_spec const *channel, int *val,
+			   int *val2, long mask)
+{
+	struct ens21x_dev *dev_data = iio_priv(indio_dev);
+	int ret = -EINVAL;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		mutex_lock(&dev_data->lock);
+		ret = ens21x_get_measurement(
+			indio_dev, channel->type == IIO_TEMP, val);
+		mutex_unlock(&dev_data->lock);
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		if (channel->type == IIO_TEMP) {
+			*val = ENS21X_CONST_TEMP_SCALE_INT;
+			*val2 = ENS21X_CONST_TEMP_SCALE_DEC;
+		} else {
+			*val = ENS21X_CONST_HUM_SCALE_INT;
+			*val2 = ENS21X_CONST_HUM_SCALE_DEC;
+		}
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+	case IIO_CHAN_INFO_OFFSET:
+		if (channel->type == IIO_TEMP) {
+			*val = ENS21X_CONST_TEMP_OFFSET_INT;
+			*val2 = ENS21X_CONST_TEMP_OFFSET_DEC;
+			ret = IIO_VAL_INT_PLUS_MICRO;
+			break;
+		}
+		*val = 0;
+		ret =  IIO_VAL_INT;
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+static const struct iio_chan_spec ens21x_channels[] = {
+	/* Temperature channel */
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_OFFSET),
+	},
+	/* Humidity channel */
+	{
+		.type = IIO_HUMIDITYRELATIVE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_OFFSET),
+	}
+};
+
+static const struct iio_info ens21x_info = {
+	.read_raw = ens21x_read_raw,
+};
+
+static int ens21x_probe(struct i2c_client *client)
+{
+	const struct i2c_device_id *id = i2c_client_get_device_id(client);
+	const struct of_device_id *match;
+	struct ens21x_dev *dev_data;
+	struct iio_dev *indio_dev;
+	uint16_t part_id_le, part_id;
+	int ret, tries;
+
+	if (!i2c_check_functionality(client->adapter,
+			I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+			I2C_FUNC_SMBUS_WRITE_BYTE |
+			I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		dev_err(&client->dev,
+			"adapter does not support some i2c transactions\n");
+		return -EOPNOTSUPP;
+	}
+
+	match = i2c_of_match_device(ens21x_of_match, client);
+	if (!match)
+		return -ENODEV;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	dev_data = iio_priv(indio_dev);
+	i2c_set_clientdata(client, indio_dev);
+	dev_data->client = client;
+	mutex_init(&dev_data->lock);
+
+	/* reset device */
+	ret = i2c_smbus_write_byte_data(client, ENS21X_REG_SYS_CTRL,
+					ENS21X_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, ENS21X_REG_SYS_CTRL, 0x00);
+	if (ret)
+		return ret;
+
+	/* wait for device to become active */
+	tries = 10;
+	while (tries-- > 0) {
+		msleep(20);
+		ret = i2c_smbus_read_byte_data(client, ENS21X_REG_SYS_STAT);
+		if (ret < 0)
+			return ret;
+		if (ret & ENS21X_SYS_STAT_SYS_ACTIVE)
+			break;
+	}
+	if (tries < 0) {
+		dev_err(&client->dev,
+			"timeout waiting for ens21x to become active\n");
+		return -EIO;
+	}
+
+	/* get part_id */
+	part_id_le = i2c_smbus_read_word_data(client, ENS21X_REG_PART_ID);
+	if (part_id_le < 0)
+		return part_id_le;
+	part_id = le16_to_cpu(part_id_le);
+
+	if (part_id != id->driver_data) {
+		dev_err(&client->dev,
+			"Part ID does not match (0x%04x != 0x%04lx)\n", part_id,
+			id->driver_data);
+		return -ENODEV;
+	}
+
+	/* reenable low power */
+	ret = i2c_smbus_write_byte_data(client, ENS21X_REG_SYS_CTRL,
+					ENS21X_SYS_CTRL_LOW_POWER_ENABLE);
+	if (ret)
+		return ret;
+
+	dev_data->part_id = part_id;
+
+	indio_dev->name = id->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = ens21x_channels;
+	indio_dev->num_channels = ARRAY_SIZE(ens21x_channels);
+	indio_dev->info = &ens21x_info;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+
+static const struct of_device_id ens21x_of_match[] = {
+	{ .compatible = "sciosense,ens210", .data = (void *)ENS210},
+	{ .compatible = "sciosense,ens210a", .data = (void *)ENS210A },
+	{ .compatible = "sciosense,ens211", .data = (void *)ENS211},
+	{ .compatible = "sciosense,ens212", .data = (void *)ENS212},
+	{ .compatible = "sciosense,ens213a", .data = (void *)ENS213A },
+	{ .compatible = "sciosense,ens215", .data = (void *)ENS215},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ens21x_of_match);
+
+static const struct i2c_device_id ens21x_id[] = {
+	{"ens210", ENS210},
+	{"ens210a", ENS210A},
+	{"ens211", ENS211},
+	{"ens212", ENS212},
+	{"ens213a", ENS213A},
+	{"ens215", ENS215},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, ens21x_id);
+
+static struct i2c_driver ens21x_driver = {
+	.probe = ens21x_probe,
+	.id_table = ens21x_id,
+	.driver = {
+		.name = "ens21x",
+		.of_match_table = ens21x_of_match,
+	},
+};
+
+module_i2c_driver(ens21x_driver);
+
+MODULE_DESCRIPTION("ScioSense ENS21x temperature and humidity sensor driver");
+MODULE_AUTHOR("Joshua Felmeden <jfelmeden@thegoodpenguin.co.uk>");
+MODULE_LICENSE("GPL");
+