18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * sgp30.c - Support for Sensirion SGP Gas Sensors
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Andreas Brauchli <andreas.brauchli@sensirion.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * I2C slave address: 0x58
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Datasheets:
108c2ecf20Sopenharmony_ci * https://www.sensirion.com/file/datasheet_sgp30
118c2ecf20Sopenharmony_ci * https://www.sensirion.com/file/datasheet_sgpc3
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * TODO:
148c2ecf20Sopenharmony_ci * - baseline support
158c2ecf20Sopenharmony_ci * - humidity compensation
168c2ecf20Sopenharmony_ci * - power mode switching (SGPC3)
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/crc8.h>
208c2ecf20Sopenharmony_ci#include <linux/delay.h>
218c2ecf20Sopenharmony_ci#include <linux/kthread.h>
228c2ecf20Sopenharmony_ci#include <linux/module.h>
238c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h>
248c2ecf20Sopenharmony_ci#include <linux/mutex.h>
258c2ecf20Sopenharmony_ci#include <linux/i2c.h>
268c2ecf20Sopenharmony_ci#include <linux/iio/iio.h>
278c2ecf20Sopenharmony_ci#include <linux/iio/sysfs.h>
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define SGP_WORD_LEN				2
308c2ecf20Sopenharmony_ci#define SGP_CRC8_POLYNOMIAL			0x31
318c2ecf20Sopenharmony_ci#define SGP_CRC8_INIT				0xff
328c2ecf20Sopenharmony_ci#define SGP_CRC8_LEN				1
338c2ecf20Sopenharmony_ci#define SGP_CMD(cmd_word)			cpu_to_be16(cmd_word)
348c2ecf20Sopenharmony_ci#define SGP_CMD_DURATION_US			12000
358c2ecf20Sopenharmony_ci#define SGP_MEASUREMENT_DURATION_US		50000
368c2ecf20Sopenharmony_ci#define SGP_CMD_LEN				SGP_WORD_LEN
378c2ecf20Sopenharmony_ci#define SGP_CMD_MAX_BUF_SIZE			(SGP_CMD_LEN + 2 * SGP_WORD_LEN)
388c2ecf20Sopenharmony_ci#define SGP_MEASUREMENT_LEN			2
398c2ecf20Sopenharmony_ci#define SGP30_MEASURE_INTERVAL_HZ		1
408c2ecf20Sopenharmony_ci#define SGPC3_MEASURE_INTERVAL_HZ		2
418c2ecf20Sopenharmony_ci#define SGP_VERS_PRODUCT(data)	((((data)->feature_set) & 0xf000) >> 12)
428c2ecf20Sopenharmony_ci#define SGP_VERS_RESERVED(data)	((((data)->feature_set) & 0x0800) >> 11)
438c2ecf20Sopenharmony_ci#define SGP_VERS_GEN(data)	((((data)->feature_set) & 0x0600) >> 9)
448c2ecf20Sopenharmony_ci#define SGP_VERS_ENG_BIT(data)	((((data)->feature_set) & 0x0100) >> 8)
458c2ecf20Sopenharmony_ci#define SGP_VERS_MAJOR(data)	((((data)->feature_set) & 0x00e0) >> 5)
468c2ecf20Sopenharmony_ci#define SGP_VERS_MINOR(data)	(((data)->feature_set) & 0x001f)
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ciDECLARE_CRC8_TABLE(sgp_crc8_table);
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cienum sgp_product_id {
518c2ecf20Sopenharmony_ci	SGP30 = 0,
528c2ecf20Sopenharmony_ci	SGPC3,
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cienum sgp30_channel_idx {
568c2ecf20Sopenharmony_ci	SGP30_IAQ_TVOC_IDX = 0,
578c2ecf20Sopenharmony_ci	SGP30_IAQ_CO2EQ_IDX,
588c2ecf20Sopenharmony_ci	SGP30_SIG_ETOH_IDX,
598c2ecf20Sopenharmony_ci	SGP30_SIG_H2_IDX,
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cienum sgpc3_channel_idx {
638c2ecf20Sopenharmony_ci	SGPC3_IAQ_TVOC_IDX = 10,
648c2ecf20Sopenharmony_ci	SGPC3_SIG_ETOH_IDX,
658c2ecf20Sopenharmony_ci};
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cienum sgp_cmd {
688c2ecf20Sopenharmony_ci	SGP_CMD_IAQ_INIT			= SGP_CMD(0x2003),
698c2ecf20Sopenharmony_ci	SGP_CMD_IAQ_MEASURE			= SGP_CMD(0x2008),
708c2ecf20Sopenharmony_ci	SGP_CMD_GET_FEATURE_SET			= SGP_CMD(0x202f),
718c2ecf20Sopenharmony_ci	SGP_CMD_GET_SERIAL_ID			= SGP_CMD(0x3682),
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	SGP30_CMD_MEASURE_SIGNAL		= SGP_CMD(0x2050),
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	SGPC3_CMD_MEASURE_RAW			= SGP_CMD(0x2046),
768c2ecf20Sopenharmony_ci};
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistruct sgp_version {
798c2ecf20Sopenharmony_ci	u8 major;
808c2ecf20Sopenharmony_ci	u8 minor;
818c2ecf20Sopenharmony_ci};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistruct sgp_crc_word {
848c2ecf20Sopenharmony_ci	__be16 value;
858c2ecf20Sopenharmony_ci	u8 crc8;
868c2ecf20Sopenharmony_ci} __attribute__((__packed__));
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ciunion sgp_reading {
898c2ecf20Sopenharmony_ci	u8 start;
908c2ecf20Sopenharmony_ci	struct sgp_crc_word raw_words[4];
918c2ecf20Sopenharmony_ci};
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cienum _iaq_buffer_state {
948c2ecf20Sopenharmony_ci	IAQ_BUFFER_EMPTY = 0,
958c2ecf20Sopenharmony_ci	IAQ_BUFFER_DEFAULT_VALS,
968c2ecf20Sopenharmony_ci	IAQ_BUFFER_VALID,
978c2ecf20Sopenharmony_ci};
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistruct sgp_data {
1008c2ecf20Sopenharmony_ci	struct i2c_client *client;
1018c2ecf20Sopenharmony_ci	struct task_struct *iaq_thread;
1028c2ecf20Sopenharmony_ci	struct mutex data_lock;
1038c2ecf20Sopenharmony_ci	unsigned long iaq_init_start_jiffies;
1048c2ecf20Sopenharmony_ci	unsigned long iaq_defval_skip_jiffies;
1058c2ecf20Sopenharmony_ci	u16 product_id;
1068c2ecf20Sopenharmony_ci	u16 feature_set;
1078c2ecf20Sopenharmony_ci	unsigned long measure_interval_jiffies;
1088c2ecf20Sopenharmony_ci	enum sgp_cmd iaq_init_cmd;
1098c2ecf20Sopenharmony_ci	enum sgp_cmd measure_iaq_cmd;
1108c2ecf20Sopenharmony_ci	enum sgp_cmd measure_gas_signals_cmd;
1118c2ecf20Sopenharmony_ci	union sgp_reading buffer;
1128c2ecf20Sopenharmony_ci	union sgp_reading iaq_buffer;
1138c2ecf20Sopenharmony_ci	enum _iaq_buffer_state iaq_buffer_state;
1148c2ecf20Sopenharmony_ci};
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistruct sgp_device {
1178c2ecf20Sopenharmony_ci	const struct iio_chan_spec *channels;
1188c2ecf20Sopenharmony_ci	int num_channels;
1198c2ecf20Sopenharmony_ci};
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic const struct sgp_version supported_versions_sgp30[] = {
1228c2ecf20Sopenharmony_ci	{
1238c2ecf20Sopenharmony_ci		.major = 1,
1248c2ecf20Sopenharmony_ci		.minor = 0,
1258c2ecf20Sopenharmony_ci	},
1268c2ecf20Sopenharmony_ci};
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic const struct sgp_version supported_versions_sgpc3[] = {
1298c2ecf20Sopenharmony_ci	{
1308c2ecf20Sopenharmony_ci		.major = 0,
1318c2ecf20Sopenharmony_ci		.minor = 4,
1328c2ecf20Sopenharmony_ci	},
1338c2ecf20Sopenharmony_ci};
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic const struct iio_chan_spec sgp30_channels[] = {
1368c2ecf20Sopenharmony_ci	{
1378c2ecf20Sopenharmony_ci		.type = IIO_CONCENTRATION,
1388c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_VOC,
1398c2ecf20Sopenharmony_ci		.modified = 1,
1408c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
1418c2ecf20Sopenharmony_ci		.address = SGP30_IAQ_TVOC_IDX,
1428c2ecf20Sopenharmony_ci	},
1438c2ecf20Sopenharmony_ci	{
1448c2ecf20Sopenharmony_ci		.type = IIO_CONCENTRATION,
1458c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_CO2,
1468c2ecf20Sopenharmony_ci		.modified = 1,
1478c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
1488c2ecf20Sopenharmony_ci		.address = SGP30_IAQ_CO2EQ_IDX,
1498c2ecf20Sopenharmony_ci	},
1508c2ecf20Sopenharmony_ci	{
1518c2ecf20Sopenharmony_ci		.type = IIO_CONCENTRATION,
1528c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_ETHANOL,
1538c2ecf20Sopenharmony_ci		.modified = 1,
1548c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
1558c2ecf20Sopenharmony_ci		.address = SGP30_SIG_ETOH_IDX,
1568c2ecf20Sopenharmony_ci	},
1578c2ecf20Sopenharmony_ci	{
1588c2ecf20Sopenharmony_ci		.type = IIO_CONCENTRATION,
1598c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_H2,
1608c2ecf20Sopenharmony_ci		.modified = 1,
1618c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
1628c2ecf20Sopenharmony_ci		.address = SGP30_SIG_H2_IDX,
1638c2ecf20Sopenharmony_ci	},
1648c2ecf20Sopenharmony_ci};
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic const struct iio_chan_spec sgpc3_channels[] = {
1678c2ecf20Sopenharmony_ci	{
1688c2ecf20Sopenharmony_ci		.type = IIO_CONCENTRATION,
1698c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_VOC,
1708c2ecf20Sopenharmony_ci		.modified = 1,
1718c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
1728c2ecf20Sopenharmony_ci		.address = SGPC3_IAQ_TVOC_IDX,
1738c2ecf20Sopenharmony_ci	},
1748c2ecf20Sopenharmony_ci	{
1758c2ecf20Sopenharmony_ci		.type = IIO_CONCENTRATION,
1768c2ecf20Sopenharmony_ci		.channel2 = IIO_MOD_ETHANOL,
1778c2ecf20Sopenharmony_ci		.modified = 1,
1788c2ecf20Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
1798c2ecf20Sopenharmony_ci		.address = SGPC3_SIG_ETOH_IDX,
1808c2ecf20Sopenharmony_ci	},
1818c2ecf20Sopenharmony_ci};
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_cistatic const struct sgp_device sgp_devices[] = {
1848c2ecf20Sopenharmony_ci	[SGP30] = {
1858c2ecf20Sopenharmony_ci		.channels = sgp30_channels,
1868c2ecf20Sopenharmony_ci		.num_channels = ARRAY_SIZE(sgp30_channels),
1878c2ecf20Sopenharmony_ci	},
1888c2ecf20Sopenharmony_ci	[SGPC3] = {
1898c2ecf20Sopenharmony_ci		.channels = sgpc3_channels,
1908c2ecf20Sopenharmony_ci		.num_channels = ARRAY_SIZE(sgpc3_channels),
1918c2ecf20Sopenharmony_ci	},
1928c2ecf20Sopenharmony_ci};
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci/**
1958c2ecf20Sopenharmony_ci * sgp_verify_buffer() - verify the checksums of the data buffer words
1968c2ecf20Sopenharmony_ci *
1978c2ecf20Sopenharmony_ci * @data:       SGP data
1988c2ecf20Sopenharmony_ci * @buf:        Raw data buffer
1998c2ecf20Sopenharmony_ci * @word_count: Num data words stored in the buffer, excluding CRC bytes
2008c2ecf20Sopenharmony_ci *
2018c2ecf20Sopenharmony_ci * Return:      0 on success, negative error otherwise.
2028c2ecf20Sopenharmony_ci */
2038c2ecf20Sopenharmony_cistatic int sgp_verify_buffer(const struct sgp_data *data,
2048c2ecf20Sopenharmony_ci			     union sgp_reading *buf, size_t word_count)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
2078c2ecf20Sopenharmony_ci	int i;
2088c2ecf20Sopenharmony_ci	u8 crc;
2098c2ecf20Sopenharmony_ci	u8 *data_buf = &buf->start;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	for (i = 0; i < size; i += SGP_WORD_LEN + SGP_CRC8_LEN) {
2128c2ecf20Sopenharmony_ci		crc = crc8(sgp_crc8_table, &data_buf[i], SGP_WORD_LEN,
2138c2ecf20Sopenharmony_ci			   SGP_CRC8_INIT);
2148c2ecf20Sopenharmony_ci		if (crc != data_buf[i + SGP_WORD_LEN]) {
2158c2ecf20Sopenharmony_ci			dev_err(&data->client->dev, "CRC error\n");
2168c2ecf20Sopenharmony_ci			return -EIO;
2178c2ecf20Sopenharmony_ci		}
2188c2ecf20Sopenharmony_ci	}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	return 0;
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci/**
2248c2ecf20Sopenharmony_ci * sgp_read_cmd() - reads data from sensor after issuing a command
2258c2ecf20Sopenharmony_ci * The caller must hold data->data_lock for the duration of the call.
2268c2ecf20Sopenharmony_ci * @data:        SGP data
2278c2ecf20Sopenharmony_ci * @cmd:         SGP Command to issue
2288c2ecf20Sopenharmony_ci * @buf:         Raw data buffer to use
2298c2ecf20Sopenharmony_ci * @word_count:  Num words to read, excluding CRC bytes
2308c2ecf20Sopenharmony_ci * @duration_us: Time taken to sensor to take a reading and data to be ready.
2318c2ecf20Sopenharmony_ci *
2328c2ecf20Sopenharmony_ci * Return:       0 on success, negative error otherwise.
2338c2ecf20Sopenharmony_ci */
2348c2ecf20Sopenharmony_cistatic int sgp_read_cmd(struct sgp_data *data, enum sgp_cmd cmd,
2358c2ecf20Sopenharmony_ci			union sgp_reading *buf, size_t word_count,
2368c2ecf20Sopenharmony_ci			unsigned long duration_us)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	int ret;
2398c2ecf20Sopenharmony_ci	struct i2c_client *client = data->client;
2408c2ecf20Sopenharmony_ci	size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN);
2418c2ecf20Sopenharmony_ci	u8 *data_buf;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	ret = i2c_master_send(client, (const char *)&cmd, SGP_CMD_LEN);
2448c2ecf20Sopenharmony_ci	if (ret != SGP_CMD_LEN)
2458c2ecf20Sopenharmony_ci		return -EIO;
2468c2ecf20Sopenharmony_ci	usleep_range(duration_us, duration_us + 1000);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	if (word_count == 0)
2498c2ecf20Sopenharmony_ci		return 0;
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	data_buf = &buf->start;
2528c2ecf20Sopenharmony_ci	ret = i2c_master_recv(client, data_buf, size);
2538c2ecf20Sopenharmony_ci	if (ret < 0)
2548c2ecf20Sopenharmony_ci		return ret;
2558c2ecf20Sopenharmony_ci	if (ret != size)
2568c2ecf20Sopenharmony_ci		return -EIO;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	return sgp_verify_buffer(data, buf, word_count);
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci/**
2628c2ecf20Sopenharmony_ci * sgp_measure_iaq() - measure and retrieve IAQ values from sensor
2638c2ecf20Sopenharmony_ci * The caller must hold data->data_lock for the duration of the call.
2648c2ecf20Sopenharmony_ci * @data:       SGP data
2658c2ecf20Sopenharmony_ci *
2668c2ecf20Sopenharmony_ci * Return:      0 on success, -EBUSY on default values, negative error
2678c2ecf20Sopenharmony_ci *              otherwise.
2688c2ecf20Sopenharmony_ci */
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistatic int sgp_measure_iaq(struct sgp_data *data)
2718c2ecf20Sopenharmony_ci{
2728c2ecf20Sopenharmony_ci	int ret;
2738c2ecf20Sopenharmony_ci	/* data contains default values */
2748c2ecf20Sopenharmony_ci	bool default_vals = !time_after(jiffies, data->iaq_init_start_jiffies +
2758c2ecf20Sopenharmony_ci						 data->iaq_defval_skip_jiffies);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	ret = sgp_read_cmd(data, data->measure_iaq_cmd, &data->iaq_buffer,
2788c2ecf20Sopenharmony_ci			   SGP_MEASUREMENT_LEN, SGP_MEASUREMENT_DURATION_US);
2798c2ecf20Sopenharmony_ci	if (ret < 0)
2808c2ecf20Sopenharmony_ci		return ret;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	data->iaq_buffer_state = IAQ_BUFFER_DEFAULT_VALS;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	if (default_vals)
2858c2ecf20Sopenharmony_ci		return -EBUSY;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	data->iaq_buffer_state = IAQ_BUFFER_VALID;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	return 0;
2908c2ecf20Sopenharmony_ci}
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_cistatic void sgp_iaq_thread_sleep_until(const struct sgp_data *data,
2938c2ecf20Sopenharmony_ci				       unsigned long sleep_jiffies)
2948c2ecf20Sopenharmony_ci{
2958c2ecf20Sopenharmony_ci	const long IAQ_POLL = 50000;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	while (!time_after(jiffies, sleep_jiffies)) {
2988c2ecf20Sopenharmony_ci		usleep_range(IAQ_POLL, IAQ_POLL + 10000);
2998c2ecf20Sopenharmony_ci		if (kthread_should_stop() || data->iaq_init_start_jiffies == 0)
3008c2ecf20Sopenharmony_ci			return;
3018c2ecf20Sopenharmony_ci	}
3028c2ecf20Sopenharmony_ci}
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_cistatic int sgp_iaq_threadfn(void *p)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	struct sgp_data *data = (struct sgp_data *)p;
3078c2ecf20Sopenharmony_ci	unsigned long next_update_jiffies;
3088c2ecf20Sopenharmony_ci	int ret;
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	while (!kthread_should_stop()) {
3118c2ecf20Sopenharmony_ci		mutex_lock(&data->data_lock);
3128c2ecf20Sopenharmony_ci		if (data->iaq_init_start_jiffies == 0) {
3138c2ecf20Sopenharmony_ci			ret = sgp_read_cmd(data, data->iaq_init_cmd, NULL, 0,
3148c2ecf20Sopenharmony_ci					   SGP_CMD_DURATION_US);
3158c2ecf20Sopenharmony_ci			if (ret < 0)
3168c2ecf20Sopenharmony_ci				goto unlock_sleep_continue;
3178c2ecf20Sopenharmony_ci			data->iaq_init_start_jiffies = jiffies;
3188c2ecf20Sopenharmony_ci		}
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci		ret = sgp_measure_iaq(data);
3218c2ecf20Sopenharmony_ci		if (ret && ret != -EBUSY) {
3228c2ecf20Sopenharmony_ci			dev_warn(&data->client->dev,
3238c2ecf20Sopenharmony_ci				 "IAQ measurement error [%d]\n", ret);
3248c2ecf20Sopenharmony_ci		}
3258c2ecf20Sopenharmony_ciunlock_sleep_continue:
3268c2ecf20Sopenharmony_ci		next_update_jiffies = jiffies + data->measure_interval_jiffies;
3278c2ecf20Sopenharmony_ci		mutex_unlock(&data->data_lock);
3288c2ecf20Sopenharmony_ci		sgp_iaq_thread_sleep_until(data, next_update_jiffies);
3298c2ecf20Sopenharmony_ci	}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	return 0;
3328c2ecf20Sopenharmony_ci}
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_cistatic int sgp_read_raw(struct iio_dev *indio_dev,
3358c2ecf20Sopenharmony_ci			struct iio_chan_spec const *chan, int *val,
3368c2ecf20Sopenharmony_ci			int *val2, long mask)
3378c2ecf20Sopenharmony_ci{
3388c2ecf20Sopenharmony_ci	struct sgp_data *data = iio_priv(indio_dev);
3398c2ecf20Sopenharmony_ci	struct sgp_crc_word *words;
3408c2ecf20Sopenharmony_ci	int ret;
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci	switch (mask) {
3438c2ecf20Sopenharmony_ci	case IIO_CHAN_INFO_PROCESSED:
3448c2ecf20Sopenharmony_ci		mutex_lock(&data->data_lock);
3458c2ecf20Sopenharmony_ci		if (data->iaq_buffer_state != IAQ_BUFFER_VALID) {
3468c2ecf20Sopenharmony_ci			mutex_unlock(&data->data_lock);
3478c2ecf20Sopenharmony_ci			return -EBUSY;
3488c2ecf20Sopenharmony_ci		}
3498c2ecf20Sopenharmony_ci		words = data->iaq_buffer.raw_words;
3508c2ecf20Sopenharmony_ci		switch (chan->address) {
3518c2ecf20Sopenharmony_ci		case SGP30_IAQ_TVOC_IDX:
3528c2ecf20Sopenharmony_ci		case SGPC3_IAQ_TVOC_IDX:
3538c2ecf20Sopenharmony_ci			*val = 0;
3548c2ecf20Sopenharmony_ci			*val2 = be16_to_cpu(words[1].value);
3558c2ecf20Sopenharmony_ci			ret = IIO_VAL_INT_PLUS_NANO;
3568c2ecf20Sopenharmony_ci			break;
3578c2ecf20Sopenharmony_ci		case SGP30_IAQ_CO2EQ_IDX:
3588c2ecf20Sopenharmony_ci			*val = 0;
3598c2ecf20Sopenharmony_ci			*val2 = be16_to_cpu(words[0].value);
3608c2ecf20Sopenharmony_ci			ret = IIO_VAL_INT_PLUS_MICRO;
3618c2ecf20Sopenharmony_ci			break;
3628c2ecf20Sopenharmony_ci		default:
3638c2ecf20Sopenharmony_ci			ret = -EINVAL;
3648c2ecf20Sopenharmony_ci			break;
3658c2ecf20Sopenharmony_ci		}
3668c2ecf20Sopenharmony_ci		mutex_unlock(&data->data_lock);
3678c2ecf20Sopenharmony_ci		break;
3688c2ecf20Sopenharmony_ci	case IIO_CHAN_INFO_RAW:
3698c2ecf20Sopenharmony_ci		mutex_lock(&data->data_lock);
3708c2ecf20Sopenharmony_ci		if (chan->address == SGPC3_SIG_ETOH_IDX) {
3718c2ecf20Sopenharmony_ci			if (data->iaq_buffer_state == IAQ_BUFFER_EMPTY)
3728c2ecf20Sopenharmony_ci				ret = -EBUSY;
3738c2ecf20Sopenharmony_ci			else
3748c2ecf20Sopenharmony_ci				ret = 0;
3758c2ecf20Sopenharmony_ci			words = data->iaq_buffer.raw_words;
3768c2ecf20Sopenharmony_ci		} else {
3778c2ecf20Sopenharmony_ci			ret = sgp_read_cmd(data, data->measure_gas_signals_cmd,
3788c2ecf20Sopenharmony_ci					   &data->buffer, SGP_MEASUREMENT_LEN,
3798c2ecf20Sopenharmony_ci					   SGP_MEASUREMENT_DURATION_US);
3808c2ecf20Sopenharmony_ci			words = data->buffer.raw_words;
3818c2ecf20Sopenharmony_ci		}
3828c2ecf20Sopenharmony_ci		if (ret) {
3838c2ecf20Sopenharmony_ci			mutex_unlock(&data->data_lock);
3848c2ecf20Sopenharmony_ci			return ret;
3858c2ecf20Sopenharmony_ci		}
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci		switch (chan->address) {
3888c2ecf20Sopenharmony_ci		case SGP30_SIG_ETOH_IDX:
3898c2ecf20Sopenharmony_ci			*val = be16_to_cpu(words[1].value);
3908c2ecf20Sopenharmony_ci			ret = IIO_VAL_INT;
3918c2ecf20Sopenharmony_ci			break;
3928c2ecf20Sopenharmony_ci		case SGPC3_SIG_ETOH_IDX:
3938c2ecf20Sopenharmony_ci		case SGP30_SIG_H2_IDX:
3948c2ecf20Sopenharmony_ci			*val = be16_to_cpu(words[0].value);
3958c2ecf20Sopenharmony_ci			ret = IIO_VAL_INT;
3968c2ecf20Sopenharmony_ci			break;
3978c2ecf20Sopenharmony_ci		default:
3988c2ecf20Sopenharmony_ci			ret = -EINVAL;
3998c2ecf20Sopenharmony_ci			break;
4008c2ecf20Sopenharmony_ci		}
4018c2ecf20Sopenharmony_ci		mutex_unlock(&data->data_lock);
4028c2ecf20Sopenharmony_ci		break;
4038c2ecf20Sopenharmony_ci	default:
4048c2ecf20Sopenharmony_ci		return -EINVAL;
4058c2ecf20Sopenharmony_ci	}
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	return ret;
4088c2ecf20Sopenharmony_ci}
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_cistatic int sgp_check_compat(struct sgp_data *data,
4118c2ecf20Sopenharmony_ci			    unsigned int product_id)
4128c2ecf20Sopenharmony_ci{
4138c2ecf20Sopenharmony_ci	struct device *dev = &data->client->dev;
4148c2ecf20Sopenharmony_ci	const struct sgp_version *supported_versions;
4158c2ecf20Sopenharmony_ci	u16 ix, num_fs;
4168c2ecf20Sopenharmony_ci	u16 product, generation, major, minor;
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci	/* driver does not match product */
4198c2ecf20Sopenharmony_ci	generation = SGP_VERS_GEN(data);
4208c2ecf20Sopenharmony_ci	if (generation != 0) {
4218c2ecf20Sopenharmony_ci		dev_err(dev,
4228c2ecf20Sopenharmony_ci			"incompatible product generation %d != 0", generation);
4238c2ecf20Sopenharmony_ci		return -ENODEV;
4248c2ecf20Sopenharmony_ci	}
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci	product = SGP_VERS_PRODUCT(data);
4278c2ecf20Sopenharmony_ci	if (product != product_id) {
4288c2ecf20Sopenharmony_ci		dev_err(dev, "sensor reports a different product: 0x%04hx\n",
4298c2ecf20Sopenharmony_ci			product);
4308c2ecf20Sopenharmony_ci		return -ENODEV;
4318c2ecf20Sopenharmony_ci	}
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_ci	if (SGP_VERS_RESERVED(data))
4348c2ecf20Sopenharmony_ci		dev_warn(dev, "reserved bit is set\n");
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	/* engineering samples are not supported: no interface guarantees */
4378c2ecf20Sopenharmony_ci	if (SGP_VERS_ENG_BIT(data))
4388c2ecf20Sopenharmony_ci		return -ENODEV;
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci	switch (product) {
4418c2ecf20Sopenharmony_ci	case SGP30:
4428c2ecf20Sopenharmony_ci		supported_versions = supported_versions_sgp30;
4438c2ecf20Sopenharmony_ci		num_fs = ARRAY_SIZE(supported_versions_sgp30);
4448c2ecf20Sopenharmony_ci		break;
4458c2ecf20Sopenharmony_ci	case SGPC3:
4468c2ecf20Sopenharmony_ci		supported_versions = supported_versions_sgpc3;
4478c2ecf20Sopenharmony_ci		num_fs = ARRAY_SIZE(supported_versions_sgpc3);
4488c2ecf20Sopenharmony_ci		break;
4498c2ecf20Sopenharmony_ci	default:
4508c2ecf20Sopenharmony_ci		return -ENODEV;
4518c2ecf20Sopenharmony_ci	}
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	major = SGP_VERS_MAJOR(data);
4548c2ecf20Sopenharmony_ci	minor = SGP_VERS_MINOR(data);
4558c2ecf20Sopenharmony_ci	for (ix = 0; ix < num_fs; ix++) {
4568c2ecf20Sopenharmony_ci		if (major == supported_versions[ix].major &&
4578c2ecf20Sopenharmony_ci		    minor >= supported_versions[ix].minor)
4588c2ecf20Sopenharmony_ci			return 0;
4598c2ecf20Sopenharmony_ci	}
4608c2ecf20Sopenharmony_ci	dev_err(dev, "unsupported sgp version: %d.%d\n", major, minor);
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	return -ENODEV;
4638c2ecf20Sopenharmony_ci}
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_cistatic void sgp_init(struct sgp_data *data)
4668c2ecf20Sopenharmony_ci{
4678c2ecf20Sopenharmony_ci	data->iaq_init_cmd = SGP_CMD_IAQ_INIT;
4688c2ecf20Sopenharmony_ci	data->iaq_init_start_jiffies = 0;
4698c2ecf20Sopenharmony_ci	data->iaq_buffer_state = IAQ_BUFFER_EMPTY;
4708c2ecf20Sopenharmony_ci	switch (SGP_VERS_PRODUCT(data)) {
4718c2ecf20Sopenharmony_ci	case SGP30:
4728c2ecf20Sopenharmony_ci		data->measure_interval_jiffies = SGP30_MEASURE_INTERVAL_HZ * HZ;
4738c2ecf20Sopenharmony_ci		data->measure_iaq_cmd = SGP_CMD_IAQ_MEASURE;
4748c2ecf20Sopenharmony_ci		data->measure_gas_signals_cmd = SGP30_CMD_MEASURE_SIGNAL;
4758c2ecf20Sopenharmony_ci		data->product_id = SGP30;
4768c2ecf20Sopenharmony_ci		data->iaq_defval_skip_jiffies = 15 * HZ;
4778c2ecf20Sopenharmony_ci		break;
4788c2ecf20Sopenharmony_ci	case SGPC3:
4798c2ecf20Sopenharmony_ci		data->measure_interval_jiffies = SGPC3_MEASURE_INTERVAL_HZ * HZ;
4808c2ecf20Sopenharmony_ci		data->measure_iaq_cmd = SGPC3_CMD_MEASURE_RAW;
4818c2ecf20Sopenharmony_ci		data->measure_gas_signals_cmd = SGPC3_CMD_MEASURE_RAW;
4828c2ecf20Sopenharmony_ci		data->product_id = SGPC3;
4838c2ecf20Sopenharmony_ci		data->iaq_defval_skip_jiffies =
4848c2ecf20Sopenharmony_ci			43 * data->measure_interval_jiffies;
4858c2ecf20Sopenharmony_ci		break;
4868c2ecf20Sopenharmony_ci	}
4878c2ecf20Sopenharmony_ci}
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_cistatic const struct iio_info sgp_info = {
4908c2ecf20Sopenharmony_ci	.read_raw	= sgp_read_raw,
4918c2ecf20Sopenharmony_ci};
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_cistatic const struct of_device_id sgp_dt_ids[] = {
4948c2ecf20Sopenharmony_ci	{ .compatible = "sensirion,sgp30", .data = (void *)SGP30 },
4958c2ecf20Sopenharmony_ci	{ .compatible = "sensirion,sgpc3", .data = (void *)SGPC3 },
4968c2ecf20Sopenharmony_ci	{ }
4978c2ecf20Sopenharmony_ci};
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_cistatic int sgp_probe(struct i2c_client *client,
5008c2ecf20Sopenharmony_ci		     const struct i2c_device_id *id)
5018c2ecf20Sopenharmony_ci{
5028c2ecf20Sopenharmony_ci	struct device *dev = &client->dev;
5038c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev;
5048c2ecf20Sopenharmony_ci	struct sgp_data *data;
5058c2ecf20Sopenharmony_ci	unsigned long product_id;
5068c2ecf20Sopenharmony_ci	int ret;
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
5098c2ecf20Sopenharmony_ci	if (!indio_dev)
5108c2ecf20Sopenharmony_ci		return -ENOMEM;
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_ci	if (dev_fwnode(dev))
5138c2ecf20Sopenharmony_ci		product_id = (unsigned long)device_get_match_data(dev);
5148c2ecf20Sopenharmony_ci	else
5158c2ecf20Sopenharmony_ci		product_id = id->driver_data;
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci	data = iio_priv(indio_dev);
5188c2ecf20Sopenharmony_ci	i2c_set_clientdata(client, indio_dev);
5198c2ecf20Sopenharmony_ci	data->client = client;
5208c2ecf20Sopenharmony_ci	crc8_populate_msb(sgp_crc8_table, SGP_CRC8_POLYNOMIAL);
5218c2ecf20Sopenharmony_ci	mutex_init(&data->data_lock);
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	/* get feature set version and write it to client data */
5248c2ecf20Sopenharmony_ci	ret = sgp_read_cmd(data, SGP_CMD_GET_FEATURE_SET, &data->buffer, 1,
5258c2ecf20Sopenharmony_ci			   SGP_CMD_DURATION_US);
5268c2ecf20Sopenharmony_ci	if (ret < 0)
5278c2ecf20Sopenharmony_ci		return ret;
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci	data->feature_set = be16_to_cpu(data->buffer.raw_words[0].value);
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	ret = sgp_check_compat(data, product_id);
5328c2ecf20Sopenharmony_ci	if (ret)
5338c2ecf20Sopenharmony_ci		return ret;
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	indio_dev->info = &sgp_info;
5368c2ecf20Sopenharmony_ci	indio_dev->name = id->name;
5378c2ecf20Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
5388c2ecf20Sopenharmony_ci	indio_dev->channels = sgp_devices[product_id].channels;
5398c2ecf20Sopenharmony_ci	indio_dev->num_channels = sgp_devices[product_id].num_channels;
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	sgp_init(data);
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_ci	ret = devm_iio_device_register(dev, indio_dev);
5448c2ecf20Sopenharmony_ci	if (ret) {
5458c2ecf20Sopenharmony_ci		dev_err(dev, "failed to register iio device\n");
5468c2ecf20Sopenharmony_ci		return ret;
5478c2ecf20Sopenharmony_ci	}
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	data->iaq_thread = kthread_run(sgp_iaq_threadfn, data,
5508c2ecf20Sopenharmony_ci				       "%s-iaq", data->client->name);
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	return 0;
5538c2ecf20Sopenharmony_ci}
5548c2ecf20Sopenharmony_ci
5558c2ecf20Sopenharmony_cistatic int sgp_remove(struct i2c_client *client)
5568c2ecf20Sopenharmony_ci{
5578c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev = i2c_get_clientdata(client);
5588c2ecf20Sopenharmony_ci	struct sgp_data *data = iio_priv(indio_dev);
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	if (data->iaq_thread)
5618c2ecf20Sopenharmony_ci		kthread_stop(data->iaq_thread);
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	return 0;
5648c2ecf20Sopenharmony_ci}
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_cistatic const struct i2c_device_id sgp_id[] = {
5678c2ecf20Sopenharmony_ci	{ "sgp30", SGP30 },
5688c2ecf20Sopenharmony_ci	{ "sgpc3", SGPC3 },
5698c2ecf20Sopenharmony_ci	{ }
5708c2ecf20Sopenharmony_ci};
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, sgp_id);
5738c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, sgp_dt_ids);
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_cistatic struct i2c_driver sgp_driver = {
5768c2ecf20Sopenharmony_ci	.driver = {
5778c2ecf20Sopenharmony_ci		.name = "sgp30",
5788c2ecf20Sopenharmony_ci		.of_match_table = sgp_dt_ids,
5798c2ecf20Sopenharmony_ci	},
5808c2ecf20Sopenharmony_ci	.probe = sgp_probe,
5818c2ecf20Sopenharmony_ci	.remove = sgp_remove,
5828c2ecf20Sopenharmony_ci	.id_table = sgp_id,
5838c2ecf20Sopenharmony_ci};
5848c2ecf20Sopenharmony_cimodule_i2c_driver(sgp_driver);
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andreas Brauchli <andreas.brauchli@sensirion.com>");
5878c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pascal Sachs <pascal.sachs@sensirion.com>");
5888c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Sensirion SGP gas sensors");
5898c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
590