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

vz89x.c (9409B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * vz89x.c - Support for SGX Sensortech MiCS VZ89X VOC sensors
      4 *
      5 * Copyright (C) 2015-2018
      6 * Author: Matt Ranostay <matt.ranostay@konsulko.com>
      7 */
      8
      9#include <linux/module.h>
     10#include <linux/mutex.h>
     11#include <linux/init.h>
     12#include <linux/i2c.h>
     13#include <linux/mod_devicetable.h>
     14
     15#include <linux/iio/iio.h>
     16#include <linux/iio/sysfs.h>
     17
     18#define VZ89X_REG_MEASUREMENT		0x09
     19#define VZ89X_REG_MEASUREMENT_RD_SIZE	6
     20#define VZ89X_REG_MEASUREMENT_WR_SIZE	3
     21
     22#define VZ89X_VOC_CO2_IDX		0
     23#define VZ89X_VOC_SHORT_IDX		1
     24#define VZ89X_VOC_TVOC_IDX		2
     25#define VZ89X_VOC_RESISTANCE_IDX	3
     26
     27#define VZ89TE_REG_MEASUREMENT		0x0c
     28#define VZ89TE_REG_MEASUREMENT_RD_SIZE	7
     29#define VZ89TE_REG_MEASUREMENT_WR_SIZE	6
     30
     31#define VZ89TE_VOC_TVOC_IDX		0
     32#define VZ89TE_VOC_CO2_IDX		1
     33#define VZ89TE_VOC_RESISTANCE_IDX	2
     34
     35enum {
     36	VZ89X,
     37	VZ89TE,
     38};
     39
     40struct vz89x_chip_data;
     41
     42struct vz89x_data {
     43	struct i2c_client *client;
     44	const struct vz89x_chip_data *chip;
     45	struct mutex lock;
     46	int (*xfer)(struct vz89x_data *data, u8 cmd);
     47
     48	bool is_valid;
     49	unsigned long last_update;
     50	u8 buffer[VZ89TE_REG_MEASUREMENT_RD_SIZE];
     51};
     52
     53struct vz89x_chip_data {
     54	bool (*valid)(struct vz89x_data *data);
     55	const struct iio_chan_spec *channels;
     56	u8 num_channels;
     57
     58	u8 cmd;
     59	u8 read_size;
     60	u8 write_size;
     61};
     62
     63static const struct iio_chan_spec vz89x_channels[] = {
     64	{
     65		.type = IIO_CONCENTRATION,
     66		.channel2 = IIO_MOD_CO2,
     67		.modified = 1,
     68		.info_mask_separate =
     69			BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
     70		.address = VZ89X_VOC_CO2_IDX,
     71	},
     72	{
     73		.type = IIO_CONCENTRATION,
     74		.channel2 = IIO_MOD_VOC,
     75		.modified = 1,
     76		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     77		.address = VZ89X_VOC_SHORT_IDX,
     78		.extend_name = "short",
     79	},
     80	{
     81		.type = IIO_CONCENTRATION,
     82		.channel2 = IIO_MOD_VOC,
     83		.modified = 1,
     84		.info_mask_separate =
     85			BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
     86		.address = VZ89X_VOC_TVOC_IDX,
     87	},
     88	{
     89		.type = IIO_RESISTANCE,
     90		.info_mask_separate =
     91			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
     92		.address = VZ89X_VOC_RESISTANCE_IDX,
     93		.scan_index = -1,
     94		.scan_type = {
     95			.endianness = IIO_LE,
     96		},
     97	},
     98};
     99
    100static const struct iio_chan_spec vz89te_channels[] = {
    101	{
    102		.type = IIO_CONCENTRATION,
    103		.channel2 = IIO_MOD_VOC,
    104		.modified = 1,
    105		.info_mask_separate =
    106			BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
    107		.address = VZ89TE_VOC_TVOC_IDX,
    108	},
    109
    110	{
    111		.type = IIO_CONCENTRATION,
    112		.channel2 = IIO_MOD_CO2,
    113		.modified = 1,
    114		.info_mask_separate =
    115			BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
    116		.address = VZ89TE_VOC_CO2_IDX,
    117	},
    118	{
    119		.type = IIO_RESISTANCE,
    120		.info_mask_separate =
    121			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
    122		.address = VZ89TE_VOC_RESISTANCE_IDX,
    123		.scan_index = -1,
    124		.scan_type = {
    125			.endianness = IIO_BE,
    126		},
    127	},
    128};
    129
    130static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689");
    131static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223");
    132
    133static struct attribute *vz89x_attributes[] = {
    134	&iio_const_attr_in_concentration_co2_scale.dev_attr.attr,
    135	&iio_const_attr_in_concentration_voc_scale.dev_attr.attr,
    136	NULL,
    137};
    138
    139static const struct attribute_group vz89x_attrs_group = {
    140	.attrs = vz89x_attributes,
    141};
    142
    143/*
    144 * Chipset sometime updates in the middle of a reading causing it to reset the
    145 * data pointer, and causing invalid reading of previous data.
    146 * We can check for this by reading MSB of the resistance reading that is
    147 * always zero, and by also confirming the VOC_short isn't zero.
    148 */
    149
    150static bool vz89x_measurement_is_valid(struct vz89x_data *data)
    151{
    152	if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0)
    153		return true;
    154
    155	return !!(data->buffer[data->chip->read_size - 1] > 0);
    156}
    157
    158/* VZ89TE device has a modified CRC-8 two complement check */
    159static bool vz89te_measurement_is_valid(struct vz89x_data *data)
    160{
    161	u8 crc = 0;
    162	int i, sum = 0;
    163
    164	for (i = 0; i < (data->chip->read_size - 1); i++) {
    165		sum = crc + data->buffer[i];
    166		crc = sum;
    167		crc += sum / 256;
    168	}
    169
    170	return !((0xff - crc) == data->buffer[data->chip->read_size - 1]);
    171}
    172
    173static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd)
    174{
    175	const struct vz89x_chip_data *chip = data->chip;
    176	struct i2c_client *client = data->client;
    177	struct i2c_msg msg[2];
    178	int ret;
    179	u8 buf[6] = { cmd, 0, 0, 0, 0, 0xf3 };
    180
    181	msg[0].addr = client->addr;
    182	msg[0].flags = client->flags;
    183	msg[0].len = chip->write_size;
    184	msg[0].buf  = (char *) &buf;
    185
    186	msg[1].addr = client->addr;
    187	msg[1].flags = client->flags | I2C_M_RD;
    188	msg[1].len = chip->read_size;
    189	msg[1].buf = (char *) &data->buffer;
    190
    191	ret = i2c_transfer(client->adapter, msg, 2);
    192
    193	return (ret == 2) ? 0 : ret;
    194}
    195
    196static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd)
    197{
    198	struct i2c_client *client = data->client;
    199	int ret;
    200	int i;
    201
    202	ret = i2c_smbus_write_word_data(client, cmd, 0);
    203	if (ret < 0)
    204		return ret;
    205
    206	for (i = 0; i < data->chip->read_size; i++) {
    207		ret = i2c_smbus_read_byte(client);
    208		if (ret < 0)
    209			return ret;
    210		data->buffer[i] = ret;
    211	}
    212
    213	return 0;
    214}
    215
    216static int vz89x_get_measurement(struct vz89x_data *data)
    217{
    218	const struct vz89x_chip_data *chip = data->chip;
    219	int ret;
    220
    221	/* sensor can only be polled once a second max per datasheet */
    222	if (!time_after(jiffies, data->last_update + HZ))
    223		return data->is_valid ? 0 : -EAGAIN;
    224
    225	data->is_valid = false;
    226	data->last_update = jiffies;
    227
    228	ret = data->xfer(data, chip->cmd);
    229	if (ret < 0)
    230		return ret;
    231
    232	ret = chip->valid(data);
    233	if (ret)
    234		return -EAGAIN;
    235
    236	data->is_valid = true;
    237
    238	return 0;
    239}
    240
    241static int vz89x_get_resistance_reading(struct vz89x_data *data,
    242					struct iio_chan_spec const *chan,
    243					int *val)
    244{
    245	u8 *tmp = &data->buffer[chan->address];
    246
    247	switch (chan->scan_type.endianness) {
    248	case IIO_LE:
    249		*val = le32_to_cpup((__le32 *) tmp) & GENMASK(23, 0);
    250		break;
    251	case IIO_BE:
    252		*val = be32_to_cpup((__be32 *) tmp) >> 8;
    253		break;
    254	default:
    255		return -EINVAL;
    256	}
    257
    258	return 0;
    259}
    260
    261static int vz89x_read_raw(struct iio_dev *indio_dev,
    262			  struct iio_chan_spec const *chan, int *val,
    263			  int *val2, long mask)
    264{
    265	struct vz89x_data *data = iio_priv(indio_dev);
    266	int ret = -EINVAL;
    267
    268	switch (mask) {
    269	case IIO_CHAN_INFO_RAW:
    270		mutex_lock(&data->lock);
    271		ret = vz89x_get_measurement(data);
    272		mutex_unlock(&data->lock);
    273
    274		if (ret)
    275			return ret;
    276
    277		switch (chan->type) {
    278		case IIO_CONCENTRATION:
    279			*val = data->buffer[chan->address];
    280			return IIO_VAL_INT;
    281		case IIO_RESISTANCE:
    282			ret = vz89x_get_resistance_reading(data, chan, val);
    283			if (!ret)
    284				return IIO_VAL_INT;
    285			break;
    286		default:
    287			return -EINVAL;
    288		}
    289		break;
    290	case IIO_CHAN_INFO_SCALE:
    291		switch (chan->type) {
    292		case IIO_RESISTANCE:
    293			*val = 10;
    294			return IIO_VAL_INT;
    295		default:
    296			return -EINVAL;
    297		}
    298		break;
    299	case IIO_CHAN_INFO_OFFSET:
    300		switch (chan->channel2) {
    301		case IIO_MOD_CO2:
    302			*val = 44;
    303			*val2 = 250000;
    304			return IIO_VAL_INT_PLUS_MICRO;
    305		case IIO_MOD_VOC:
    306			*val = -13;
    307			return IIO_VAL_INT;
    308		default:
    309			return -EINVAL;
    310		}
    311	}
    312
    313	return ret;
    314}
    315
    316static const struct iio_info vz89x_info = {
    317	.attrs		= &vz89x_attrs_group,
    318	.read_raw	= vz89x_read_raw,
    319};
    320
    321static const struct vz89x_chip_data vz89x_chips[] = {
    322	{
    323		.valid = vz89x_measurement_is_valid,
    324
    325		.cmd = VZ89X_REG_MEASUREMENT,
    326		.read_size = VZ89X_REG_MEASUREMENT_RD_SIZE,
    327		.write_size = VZ89X_REG_MEASUREMENT_WR_SIZE,
    328
    329		.channels = vz89x_channels,
    330		.num_channels = ARRAY_SIZE(vz89x_channels),
    331	},
    332	{
    333		.valid = vz89te_measurement_is_valid,
    334
    335		.cmd = VZ89TE_REG_MEASUREMENT,
    336		.read_size = VZ89TE_REG_MEASUREMENT_RD_SIZE,
    337		.write_size = VZ89TE_REG_MEASUREMENT_WR_SIZE,
    338
    339		.channels = vz89te_channels,
    340		.num_channels = ARRAY_SIZE(vz89te_channels),
    341	},
    342};
    343
    344static const struct of_device_id vz89x_dt_ids[] = {
    345	{ .compatible = "sgx,vz89x", .data = (void *) VZ89X },
    346	{ .compatible = "sgx,vz89te", .data = (void *) VZ89TE },
    347	{ }
    348};
    349MODULE_DEVICE_TABLE(of, vz89x_dt_ids);
    350
    351static int vz89x_probe(struct i2c_client *client,
    352		       const struct i2c_device_id *id)
    353{
    354	struct device *dev = &client->dev;
    355	struct iio_dev *indio_dev;
    356	struct vz89x_data *data;
    357	int chip_id;
    358
    359	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
    360	if (!indio_dev)
    361		return -ENOMEM;
    362	data = iio_priv(indio_dev);
    363
    364	if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
    365		data->xfer = vz89x_i2c_xfer;
    366	else if (i2c_check_functionality(client->adapter,
    367				I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
    368		data->xfer = vz89x_smbus_xfer;
    369	else
    370		return -EOPNOTSUPP;
    371
    372	if (!dev_fwnode(dev))
    373		chip_id = id->driver_data;
    374	else
    375		chip_id = (unsigned long)device_get_match_data(dev);
    376
    377	i2c_set_clientdata(client, indio_dev);
    378	data->client = client;
    379	data->chip = &vz89x_chips[chip_id];
    380	data->last_update = jiffies - HZ;
    381	mutex_init(&data->lock);
    382
    383	indio_dev->info = &vz89x_info;
    384	indio_dev->name = dev_name(dev);
    385	indio_dev->modes = INDIO_DIRECT_MODE;
    386
    387	indio_dev->channels = data->chip->channels;
    388	indio_dev->num_channels = data->chip->num_channels;
    389
    390	return devm_iio_device_register(dev, indio_dev);
    391}
    392
    393static const struct i2c_device_id vz89x_id[] = {
    394	{ "vz89x", VZ89X },
    395	{ "vz89te", VZ89TE },
    396	{ }
    397};
    398MODULE_DEVICE_TABLE(i2c, vz89x_id);
    399
    400static struct i2c_driver vz89x_driver = {
    401	.driver = {
    402		.name	= "vz89x",
    403		.of_match_table = vz89x_dt_ids,
    404	},
    405	.probe = vz89x_probe,
    406	.id_table = vz89x_id,
    407};
    408module_i2c_driver(vz89x_driver);
    409
    410MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
    411MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors");
    412MODULE_LICENSE("GPL v2");