cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

sgp40.c (9434B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * sgp40.c - Support for Sensirion SGP40 Gas Sensor
      4 *
      5 * Copyright (C) 2021 Andreas Klinger <ak@it-klinger.de>
      6 *
      7 * I2C slave address: 0x59
      8 *
      9 * Datasheet can be found here:
     10 * https://www.sensirion.com/file/datasheet_sgp40
     11 *
     12 * There are two functionalities supported:
     13 *
     14 * 1) read raw logarithmic resistance value from sensor
     15 *    --> useful to pass it to the algorithm of the sensor vendor for
     16 *    measuring deteriorations and improvements of air quality.
     17 *
     18 * 2) calculate an estimated absolute voc index (0 - 500 index points) for
     19 *    measuring the air quality.
     20 *    For this purpose the value of the resistance for which the voc index
     21 *    will be 250 can be set up using calibbias.
     22 *
     23 * Compensation values of relative humidity and temperature can be set up
     24 * by writing to the out values of temp and humidityrelative.
     25 */
     26
     27#include <linux/delay.h>
     28#include <linux/crc8.h>
     29#include <linux/module.h>
     30#include <linux/mutex.h>
     31#include <linux/i2c.h>
     32#include <linux/iio/iio.h>
     33
     34/*
     35 * floating point calculation of voc is done as integer
     36 * where numbers are multiplied by 1 << SGP40_CALC_POWER
     37 */
     38#define SGP40_CALC_POWER	14
     39
     40#define SGP40_CRC8_POLYNOMIAL	0x31
     41#define SGP40_CRC8_INIT		0xff
     42
     43DECLARE_CRC8_TABLE(sgp40_crc8_table);
     44
     45struct sgp40_data {
     46	struct device		*dev;
     47	struct i2c_client	*client;
     48	int			rht;
     49	int			temp;
     50	int			res_calibbias;
     51	/* Prevent concurrent access to rht, tmp, calibbias */
     52	struct mutex		lock;
     53};
     54
     55struct sgp40_tg_measure {
     56	u8	command[2];
     57	__be16	rht_ticks;
     58	u8	rht_crc;
     59	__be16	temp_ticks;
     60	u8	temp_crc;
     61} __packed;
     62
     63struct sgp40_tg_result {
     64	__be16	res_ticks;
     65	u8	res_crc;
     66} __packed;
     67
     68static const struct iio_chan_spec sgp40_channels[] = {
     69	{
     70		.type = IIO_CONCENTRATION,
     71		.channel2 = IIO_MOD_VOC,
     72		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
     73	},
     74	{
     75		.type = IIO_RESISTANCE,
     76		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
     77			BIT(IIO_CHAN_INFO_CALIBBIAS),
     78	},
     79	{
     80		.type = IIO_TEMP,
     81		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     82		.output = 1,
     83	},
     84	{
     85		.type = IIO_HUMIDITYRELATIVE,
     86		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     87		.output = 1,
     88	},
     89};
     90
     91/*
     92 * taylor approximation of e^x:
     93 * y = 1 + x + x^2 / 2 + x^3 / 6 + x^4 / 24 + ... + x^n / n!
     94 *
     95 * Because we are calculating x real value multiplied by 2^power we get
     96 * an additional 2^power^n to divide for every element. For a reasonable
     97 * precision this would overflow after a few iterations. Therefore we
     98 * divide the x^n part whenever its about to overflow (xmax).
     99 */
    100
    101static u32 sgp40_exp(int exp, u32 power, u32 rounds)
    102{
    103        u32 x, y, xp;
    104        u32 factorial, divider, xmax;
    105        int sign = 1;
    106	int i;
    107
    108        if (exp == 0)
    109                return 1 << power;
    110        else if (exp < 0) {
    111                sign = -1;
    112                exp *= -1;
    113        }
    114
    115        xmax = 0x7FFFFFFF / exp;
    116        x = exp;
    117        xp = 1;
    118        factorial = 1;
    119        y = 1 << power;
    120        divider = 0;
    121
    122        for (i = 1; i <= rounds; i++) {
    123                xp *= x;
    124                factorial *= i;
    125                y += (xp >> divider) / factorial;
    126                divider += power;
    127                /* divide when next multiplication would overflow */
    128                if (xp >= xmax) {
    129                        xp >>= power;
    130                        divider -= power;
    131                }
    132        }
    133
    134        if (sign == -1)
    135                return (1 << (power * 2)) / y;
    136        else
    137                return y;
    138}
    139
    140static int sgp40_calc_voc(struct sgp40_data *data, u16 resistance_raw, int *voc)
    141{
    142	int x;
    143	u32 exp = 0;
    144
    145	/* we calculate as a multiple of 16384 (2^14) */
    146	mutex_lock(&data->lock);
    147	x = ((int)resistance_raw - data->res_calibbias) * 106;
    148	mutex_unlock(&data->lock);
    149
    150	/* voc = 500 / (1 + e^x) */
    151	exp = sgp40_exp(x, SGP40_CALC_POWER, 18);
    152	*voc = 500 * ((1 << (SGP40_CALC_POWER * 2)) / ((1<<SGP40_CALC_POWER) + exp));
    153
    154	dev_dbg(data->dev, "raw: %d res_calibbias: %d x: %d exp: %d voc: %d\n",
    155				resistance_raw, data->res_calibbias, x, exp, *voc);
    156
    157	return 0;
    158}
    159
    160static int sgp40_measure_resistance_raw(struct sgp40_data *data, u16 *resistance_raw)
    161{
    162	int ret;
    163	struct i2c_client *client = data->client;
    164	u32 ticks;
    165	u16 ticks16;
    166	u8 crc;
    167	struct sgp40_tg_measure tg = {.command = {0x26, 0x0F}};
    168	struct sgp40_tg_result tgres;
    169
    170	mutex_lock(&data->lock);
    171
    172	ticks = (data->rht / 10) * 65535 / 10000;
    173	ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between 0 .. 100 %rH */
    174	tg.rht_ticks = cpu_to_be16(ticks16);
    175	tg.rht_crc = crc8(sgp40_crc8_table, (u8 *)&tg.rht_ticks, 2, SGP40_CRC8_INIT);
    176
    177	ticks = ((data->temp + 45000) / 10 ) * 65535 / 17500;
    178	ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between -45 .. +130 °C */
    179	tg.temp_ticks = cpu_to_be16(ticks16);
    180	tg.temp_crc = crc8(sgp40_crc8_table, (u8 *)&tg.temp_ticks, 2, SGP40_CRC8_INIT);
    181
    182	mutex_unlock(&data->lock);
    183
    184	ret = i2c_master_send(client, (const char *)&tg, sizeof(tg));
    185	if (ret != sizeof(tg)) {
    186		dev_warn(data->dev, "i2c_master_send ret: %d sizeof: %zu\n", ret, sizeof(tg));
    187		return -EIO;
    188	}
    189	msleep(30);
    190
    191	ret = i2c_master_recv(client, (u8 *)&tgres, sizeof(tgres));
    192	if (ret < 0)
    193		return ret;
    194	if (ret != sizeof(tgres)) {
    195		dev_warn(data->dev, "i2c_master_recv ret: %d sizeof: %zu\n", ret, sizeof(tgres));
    196		return -EIO;
    197	}
    198
    199	crc = crc8(sgp40_crc8_table, (u8 *)&tgres.res_ticks, 2, SGP40_CRC8_INIT);
    200	if (crc != tgres.res_crc) {
    201		dev_err(data->dev, "CRC error while measure-raw\n");
    202		return -EIO;
    203	}
    204
    205	*resistance_raw = be16_to_cpu(tgres.res_ticks);
    206
    207	return 0;
    208}
    209
    210static int sgp40_read_raw(struct iio_dev *indio_dev,
    211			struct iio_chan_spec const *chan, int *val,
    212			int *val2, long mask)
    213{
    214	struct sgp40_data *data = iio_priv(indio_dev);
    215	int ret, voc;
    216	u16 resistance_raw;
    217
    218	switch (mask) {
    219	case IIO_CHAN_INFO_RAW:
    220		switch (chan->type) {
    221		case IIO_RESISTANCE:
    222			ret = sgp40_measure_resistance_raw(data, &resistance_raw);
    223			if (ret)
    224				return ret;
    225
    226			*val = resistance_raw;
    227			return IIO_VAL_INT;
    228		case IIO_TEMP:
    229			mutex_lock(&data->lock);
    230			*val = data->temp;
    231			mutex_unlock(&data->lock);
    232			return IIO_VAL_INT;
    233		case IIO_HUMIDITYRELATIVE:
    234			mutex_lock(&data->lock);
    235			*val = data->rht;
    236			mutex_unlock(&data->lock);
    237			return IIO_VAL_INT;
    238		default:
    239			return -EINVAL;
    240		}
    241	case IIO_CHAN_INFO_PROCESSED:
    242		ret = sgp40_measure_resistance_raw(data, &resistance_raw);
    243		if (ret)
    244			return ret;
    245
    246		ret = sgp40_calc_voc(data, resistance_raw, &voc);
    247		if (ret)
    248			return ret;
    249
    250		*val = voc / (1 << SGP40_CALC_POWER);
    251		/*
    252		 * calculation should fit into integer, where:
    253		 * voc <= (500 * 2^SGP40_CALC_POWER) = 8192000
    254		 * (with SGP40_CALC_POWER = 14)
    255		 */
    256		*val2 = ((voc % (1 << SGP40_CALC_POWER)) * 244) / (1 << (SGP40_CALC_POWER - 12));
    257		dev_dbg(data->dev, "voc: %d val: %d.%06d\n", voc, *val, *val2);
    258		return IIO_VAL_INT_PLUS_MICRO;
    259	case IIO_CHAN_INFO_CALIBBIAS:
    260		mutex_lock(&data->lock);
    261		*val = data->res_calibbias;
    262		mutex_unlock(&data->lock);
    263		return IIO_VAL_INT;
    264	default:
    265		return -EINVAL;
    266	}
    267}
    268
    269static int sgp40_write_raw(struct iio_dev *indio_dev,
    270			struct iio_chan_spec const *chan, int val,
    271			int val2, long mask)
    272{
    273	struct sgp40_data *data = iio_priv(indio_dev);
    274
    275	switch (mask) {
    276	case IIO_CHAN_INFO_RAW:
    277		switch (chan->type) {
    278		case IIO_TEMP:
    279			if ((val < -45000) || (val > 130000))
    280				return -EINVAL;
    281
    282			mutex_lock(&data->lock);
    283			data->temp = val;
    284			mutex_unlock(&data->lock);
    285			return 0;
    286		case IIO_HUMIDITYRELATIVE:
    287			if ((val < 0) || (val > 100000))
    288				return -EINVAL;
    289
    290			mutex_lock(&data->lock);
    291			data->rht = val;
    292			mutex_unlock(&data->lock);
    293			return 0;
    294		default:
    295			return -EINVAL;
    296		}
    297	case IIO_CHAN_INFO_CALIBBIAS:
    298		if ((val < 20000) || (val > 52768))
    299			return -EINVAL;
    300
    301		mutex_lock(&data->lock);
    302		data->res_calibbias = val;
    303		mutex_unlock(&data->lock);
    304		return 0;
    305	}
    306	return -EINVAL;
    307}
    308
    309static const struct iio_info sgp40_info = {
    310	.read_raw	= sgp40_read_raw,
    311	.write_raw	= sgp40_write_raw,
    312};
    313
    314static int sgp40_probe(struct i2c_client *client,
    315		     const struct i2c_device_id *id)
    316{
    317	struct device *dev = &client->dev;
    318	struct iio_dev *indio_dev;
    319	struct sgp40_data *data;
    320	int ret;
    321
    322	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
    323	if (!indio_dev)
    324		return -ENOMEM;
    325
    326	data = iio_priv(indio_dev);
    327	data->client = client;
    328	data->dev = dev;
    329
    330	crc8_populate_msb(sgp40_crc8_table, SGP40_CRC8_POLYNOMIAL);
    331
    332	mutex_init(&data->lock);
    333
    334	/* set default values */
    335	data->rht = 50000;		/* 50 % */
    336	data->temp = 25000;		/* 25 °C */
    337	data->res_calibbias = 30000;	/* resistance raw value for voc index of 250 */
    338
    339	indio_dev->info = &sgp40_info;
    340	indio_dev->name = id->name;
    341	indio_dev->modes = INDIO_DIRECT_MODE;
    342	indio_dev->channels = sgp40_channels;
    343	indio_dev->num_channels = ARRAY_SIZE(sgp40_channels);
    344
    345	ret = devm_iio_device_register(dev, indio_dev);
    346	if (ret)
    347		dev_err(dev, "failed to register iio device\n");
    348
    349	return ret;
    350}
    351
    352static const struct i2c_device_id sgp40_id[] = {
    353	{ "sgp40" },
    354	{ }
    355};
    356
    357MODULE_DEVICE_TABLE(i2c, sgp40_id);
    358
    359static const struct of_device_id sgp40_dt_ids[] = {
    360	{ .compatible = "sensirion,sgp40" },
    361	{ }
    362};
    363
    364MODULE_DEVICE_TABLE(of, sgp40_dt_ids);
    365
    366static struct i2c_driver sgp40_driver = {
    367	.driver = {
    368		.name = "sgp40",
    369		.of_match_table = sgp40_dt_ids,
    370	},
    371	.probe = sgp40_probe,
    372	.id_table = sgp40_id,
    373};
    374module_i2c_driver(sgp40_driver);
    375
    376MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>");
    377MODULE_DESCRIPTION("Sensirion SGP40 gas sensor");
    378MODULE_LICENSE("GPL v2");