162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * si7020.c - Silicon Labs Si7013/20/21 Relative Humidity and Temp Sensors
462306a36Sopenharmony_ci * Copyright (c) 2013,2014  Uplogix, Inc.
562306a36Sopenharmony_ci * David Barksdale <dbarksdale@uplogix.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci/*
962306a36Sopenharmony_ci * The Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors
1062306a36Sopenharmony_ci * are i2c devices which have an identical programming interface for
1162306a36Sopenharmony_ci * measuring relative humidity and temperature. The Si7013 has an additional
1262306a36Sopenharmony_ci * temperature input which this driver does not support.
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * Data Sheets:
1562306a36Sopenharmony_ci *   Si7013: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7013.pdf
1662306a36Sopenharmony_ci *   Si7020: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7020.pdf
1762306a36Sopenharmony_ci *   Si7021: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7021.pdf
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <linux/delay.h>
2162306a36Sopenharmony_ci#include <linux/i2c.h>
2262306a36Sopenharmony_ci#include <linux/module.h>
2362306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <linux/sysfs.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include <linux/iio/iio.h>
2862306a36Sopenharmony_ci#include <linux/iio/sysfs.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/* Measure Relative Humidity, Hold Master Mode */
3162306a36Sopenharmony_ci#define SI7020CMD_RH_HOLD	0xE5
3262306a36Sopenharmony_ci/* Measure Temperature, Hold Master Mode */
3362306a36Sopenharmony_ci#define SI7020CMD_TEMP_HOLD	0xE3
3462306a36Sopenharmony_ci/* Software Reset */
3562306a36Sopenharmony_ci#define SI7020CMD_RESET		0xFE
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic int si7020_read_raw(struct iio_dev *indio_dev,
3862306a36Sopenharmony_ci			   struct iio_chan_spec const *chan, int *val,
3962306a36Sopenharmony_ci			   int *val2, long mask)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	struct i2c_client **client = iio_priv(indio_dev);
4262306a36Sopenharmony_ci	int ret;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	switch (mask) {
4562306a36Sopenharmony_ci	case IIO_CHAN_INFO_RAW:
4662306a36Sopenharmony_ci		ret = i2c_smbus_read_word_swapped(*client,
4762306a36Sopenharmony_ci						  chan->type == IIO_TEMP ?
4862306a36Sopenharmony_ci						  SI7020CMD_TEMP_HOLD :
4962306a36Sopenharmony_ci						  SI7020CMD_RH_HOLD);
5062306a36Sopenharmony_ci		if (ret < 0)
5162306a36Sopenharmony_ci			return ret;
5262306a36Sopenharmony_ci		*val = ret >> 2;
5362306a36Sopenharmony_ci		/*
5462306a36Sopenharmony_ci		 * Humidity values can slightly exceed the 0-100%RH
5562306a36Sopenharmony_ci		 * range and should be corrected by software
5662306a36Sopenharmony_ci		 */
5762306a36Sopenharmony_ci		if (chan->type == IIO_HUMIDITYRELATIVE)
5862306a36Sopenharmony_ci			*val = clamp_val(*val, 786, 13893);
5962306a36Sopenharmony_ci		return IIO_VAL_INT;
6062306a36Sopenharmony_ci	case IIO_CHAN_INFO_SCALE:
6162306a36Sopenharmony_ci		if (chan->type == IIO_TEMP)
6262306a36Sopenharmony_ci			*val = 175720; /* = 175.72 * 1000 */
6362306a36Sopenharmony_ci		else
6462306a36Sopenharmony_ci			*val = 125 * 1000;
6562306a36Sopenharmony_ci		*val2 = 65536 >> 2;
6662306a36Sopenharmony_ci		return IIO_VAL_FRACTIONAL;
6762306a36Sopenharmony_ci	case IIO_CHAN_INFO_OFFSET:
6862306a36Sopenharmony_ci		/*
6962306a36Sopenharmony_ci		 * Since iio_convert_raw_to_processed_unlocked assumes offset
7062306a36Sopenharmony_ci		 * is an integer we have to round these values and lose
7162306a36Sopenharmony_ci		 * accuracy.
7262306a36Sopenharmony_ci		 * Relative humidity will be 0.0032959% too high and
7362306a36Sopenharmony_ci		 * temperature will be 0.00277344 degrees too high.
7462306a36Sopenharmony_ci		 * This is no big deal because it's within the accuracy of the
7562306a36Sopenharmony_ci		 * sensor.
7662306a36Sopenharmony_ci		 */
7762306a36Sopenharmony_ci		if (chan->type == IIO_TEMP)
7862306a36Sopenharmony_ci			*val = -4368; /* = -46.85 * (65536 >> 2) / 175.72 */
7962306a36Sopenharmony_ci		else
8062306a36Sopenharmony_ci			*val = -786; /* = -6 * (65536 >> 2) / 125 */
8162306a36Sopenharmony_ci		return IIO_VAL_INT;
8262306a36Sopenharmony_ci	default:
8362306a36Sopenharmony_ci		break;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	return -EINVAL;
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic const struct iio_chan_spec si7020_channels[] = {
9062306a36Sopenharmony_ci	{
9162306a36Sopenharmony_ci		.type = IIO_HUMIDITYRELATIVE,
9262306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
9362306a36Sopenharmony_ci			BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),
9462306a36Sopenharmony_ci	},
9562306a36Sopenharmony_ci	{
9662306a36Sopenharmony_ci		.type = IIO_TEMP,
9762306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
9862306a36Sopenharmony_ci			BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci};
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic const struct iio_info si7020_info = {
10362306a36Sopenharmony_ci	.read_raw = si7020_read_raw,
10462306a36Sopenharmony_ci};
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int si7020_probe(struct i2c_client *client)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct iio_dev *indio_dev;
10962306a36Sopenharmony_ci	struct i2c_client **data;
11062306a36Sopenharmony_ci	int ret;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (!i2c_check_functionality(client->adapter,
11362306a36Sopenharmony_ci				     I2C_FUNC_SMBUS_WRITE_BYTE |
11462306a36Sopenharmony_ci				     I2C_FUNC_SMBUS_READ_WORD_DATA))
11562306a36Sopenharmony_ci		return -EOPNOTSUPP;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	/* Reset device, loads default settings. */
11862306a36Sopenharmony_ci	ret = i2c_smbus_write_byte(client, SI7020CMD_RESET);
11962306a36Sopenharmony_ci	if (ret < 0)
12062306a36Sopenharmony_ci		return ret;
12162306a36Sopenharmony_ci	/* Wait the maximum power-up time after software reset. */
12262306a36Sopenharmony_ci	msleep(15);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
12562306a36Sopenharmony_ci	if (!indio_dev)
12662306a36Sopenharmony_ci		return -ENOMEM;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	data = iio_priv(indio_dev);
12962306a36Sopenharmony_ci	*data = client;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	indio_dev->name = dev_name(&client->dev);
13262306a36Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
13362306a36Sopenharmony_ci	indio_dev->info = &si7020_info;
13462306a36Sopenharmony_ci	indio_dev->channels = si7020_channels;
13562306a36Sopenharmony_ci	indio_dev->num_channels = ARRAY_SIZE(si7020_channels);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return devm_iio_device_register(&client->dev, indio_dev);
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic const struct i2c_device_id si7020_id[] = {
14162306a36Sopenharmony_ci	{ "si7020", 0 },
14262306a36Sopenharmony_ci	{ "th06", 0 },
14362306a36Sopenharmony_ci	{ }
14462306a36Sopenharmony_ci};
14562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, si7020_id);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic const struct of_device_id si7020_dt_ids[] = {
14862306a36Sopenharmony_ci	{ .compatible = "silabs,si7020" },
14962306a36Sopenharmony_ci	{ }
15062306a36Sopenharmony_ci};
15162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, si7020_dt_ids);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic struct i2c_driver si7020_driver = {
15462306a36Sopenharmony_ci	.driver = {
15562306a36Sopenharmony_ci		.name = "si7020",
15662306a36Sopenharmony_ci		.of_match_table = si7020_dt_ids,
15762306a36Sopenharmony_ci	},
15862306a36Sopenharmony_ci	.probe		= si7020_probe,
15962306a36Sopenharmony_ci	.id_table	= si7020_id,
16062306a36Sopenharmony_ci};
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cimodule_i2c_driver(si7020_driver);
16362306a36Sopenharmony_ciMODULE_DESCRIPTION("Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors");
16462306a36Sopenharmony_ciMODULE_AUTHOR("David Barksdale <dbarksdale@uplogix.com>");
16562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
166