18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * atlas-ezo-sensor.c - Support for Atlas Scientific EZO sensors
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2020 Konsulko Group
68c2ecf20Sopenharmony_ci * Author: Matt Ranostay <matt.ranostay@konsulko.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/delay.h>
128c2ecf20Sopenharmony_ci#include <linux/mutex.h>
138c2ecf20Sopenharmony_ci#include <linux/err.h>
148c2ecf20Sopenharmony_ci#include <linux/i2c.h>
158c2ecf20Sopenharmony_ci#include <linux/of_device.h>
168c2ecf20Sopenharmony_ci#include <linux/iio/iio.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define ATLAS_EZO_DRV_NAME		"atlas-ezo-sensor"
198c2ecf20Sopenharmony_ci#define ATLAS_INT_TIME_IN_MS		950
208c2ecf20Sopenharmony_ci#define ATLAS_INT_HUM_TIME_IN_MS	350
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cienum {
238c2ecf20Sopenharmony_ci	ATLAS_CO2_EZO,
248c2ecf20Sopenharmony_ci	ATLAS_O2_EZO,
258c2ecf20Sopenharmony_ci	ATLAS_HUM_EZO,
268c2ecf20Sopenharmony_ci};
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistruct atlas_ezo_device {
298c2ecf20Sopenharmony_ci	const struct iio_chan_spec *channels;
308c2ecf20Sopenharmony_ci	int num_channels;
318c2ecf20Sopenharmony_ci	int delay;
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistruct atlas_ezo_data {
358c2ecf20Sopenharmony_ci	struct i2c_client *client;
368c2ecf20Sopenharmony_ci	struct atlas_ezo_device *chip;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	/* lock to avoid multiple concurrent read calls */
398c2ecf20Sopenharmony_ci	struct mutex lock;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	u8 buffer[8];
428c2ecf20Sopenharmony_ci};
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#define ATLAS_CONCENTRATION_CHANNEL(_modifier) \
458c2ecf20Sopenharmony_ci	{ \
468c2ecf20Sopenharmony_ci		.type = IIO_CONCENTRATION, \
478c2ecf20Sopenharmony_ci		.modified = 1,\
488c2ecf20Sopenharmony_ci		.channel2 = _modifier, \
498c2ecf20Sopenharmony_ci		.info_mask_separate = \
508c2ecf20Sopenharmony_ci			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \
518c2ecf20Sopenharmony_ci		.scan_index = 0, \
528c2ecf20Sopenharmony_ci		.scan_type =  { \
538c2ecf20Sopenharmony_ci			.sign = 'u', \
548c2ecf20Sopenharmony_ci			.realbits = 32, \
558c2ecf20Sopenharmony_ci			.storagebits = 32, \
568c2ecf20Sopenharmony_ci			.endianness = IIO_CPU, \
578c2ecf20Sopenharmony_ci		}, \
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic const struct iio_chan_spec atlas_co2_ezo_channels[] = {
618c2ecf20Sopenharmony_ci	ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_CO2),
628c2ecf20Sopenharmony_ci};
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic const struct iio_chan_spec atlas_o2_ezo_channels[] = {
658c2ecf20Sopenharmony_ci	ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_O2),
668c2ecf20Sopenharmony_ci};
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic const struct iio_chan_spec atlas_hum_ezo_channels[] = {
698c2ecf20Sopenharmony_ci	{
708c2ecf20Sopenharmony_ci		.type = IIO_HUMIDITYRELATIVE,
718c2ecf20Sopenharmony_ci		.info_mask_separate =
728c2ecf20Sopenharmony_ci			BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
738c2ecf20Sopenharmony_ci		.scan_index = 0,
748c2ecf20Sopenharmony_ci		.scan_type =  {
758c2ecf20Sopenharmony_ci			.sign = 'u',
768c2ecf20Sopenharmony_ci			.realbits = 32,
778c2ecf20Sopenharmony_ci			.storagebits = 32,
788c2ecf20Sopenharmony_ci			.endianness = IIO_CPU,
798c2ecf20Sopenharmony_ci		},
808c2ecf20Sopenharmony_ci	},
818c2ecf20Sopenharmony_ci};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic struct atlas_ezo_device atlas_ezo_devices[] = {
848c2ecf20Sopenharmony_ci	[ATLAS_CO2_EZO] = {
858c2ecf20Sopenharmony_ci		.channels = atlas_co2_ezo_channels,
868c2ecf20Sopenharmony_ci		.num_channels = 1,
878c2ecf20Sopenharmony_ci		.delay = ATLAS_INT_TIME_IN_MS,
888c2ecf20Sopenharmony_ci	},
898c2ecf20Sopenharmony_ci	[ATLAS_O2_EZO] = {
908c2ecf20Sopenharmony_ci		.channels = atlas_o2_ezo_channels,
918c2ecf20Sopenharmony_ci		.num_channels = 1,
928c2ecf20Sopenharmony_ci		.delay = ATLAS_INT_TIME_IN_MS,
938c2ecf20Sopenharmony_ci	},
948c2ecf20Sopenharmony_ci	[ATLAS_HUM_EZO] = {
958c2ecf20Sopenharmony_ci		.channels = atlas_hum_ezo_channels,
968c2ecf20Sopenharmony_ci		.num_channels = 1,
978c2ecf20Sopenharmony_ci		.delay = ATLAS_INT_HUM_TIME_IN_MS,
988c2ecf20Sopenharmony_ci	},
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic void atlas_ezo_sanitize(char *buf)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	char *ptr = strchr(buf, '.');
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	if (!ptr)
1068c2ecf20Sopenharmony_ci		return;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	memmove(ptr, ptr + 1, strlen(ptr));
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic int atlas_ezo_read_raw(struct iio_dev *indio_dev,
1128c2ecf20Sopenharmony_ci			  struct iio_chan_spec const *chan,
1138c2ecf20Sopenharmony_ci			  int *val, int *val2, long mask)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	struct atlas_ezo_data *data = iio_priv(indio_dev);
1168c2ecf20Sopenharmony_ci	struct i2c_client *client = data->client;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (chan->type != IIO_CONCENTRATION)
1198c2ecf20Sopenharmony_ci		return -EINVAL;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	switch (mask) {
1228c2ecf20Sopenharmony_ci	case IIO_CHAN_INFO_RAW: {
1238c2ecf20Sopenharmony_ci		int ret;
1248c2ecf20Sopenharmony_ci		long tmp;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci		mutex_lock(&data->lock);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci		tmp = i2c_smbus_write_byte(client, 'R');
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci		if (tmp < 0) {
1318c2ecf20Sopenharmony_ci			mutex_unlock(&data->lock);
1328c2ecf20Sopenharmony_ci			return tmp;
1338c2ecf20Sopenharmony_ci		}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci		msleep(data->chip->delay);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci		tmp = i2c_master_recv(client, data->buffer, sizeof(data->buffer));
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci		if (tmp < 0 || data->buffer[0] != 1) {
1408c2ecf20Sopenharmony_ci			mutex_unlock(&data->lock);
1418c2ecf20Sopenharmony_ci			return -EBUSY;
1428c2ecf20Sopenharmony_ci		}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci		/* removing floating point for fixed number representation */
1458c2ecf20Sopenharmony_ci		atlas_ezo_sanitize(data->buffer + 2);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		ret = kstrtol(data->buffer + 1, 10, &tmp);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci		*val = tmp;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci		mutex_unlock(&data->lock);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci		return ret ? ret : IIO_VAL_INT;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci	case IIO_CHAN_INFO_SCALE:
1568c2ecf20Sopenharmony_ci		switch (chan->type) {
1578c2ecf20Sopenharmony_ci		case IIO_HUMIDITYRELATIVE:
1588c2ecf20Sopenharmony_ci			*val = 10;
1598c2ecf20Sopenharmony_ci			return IIO_VAL_INT;
1608c2ecf20Sopenharmony_ci		case IIO_CONCENTRATION:
1618c2ecf20Sopenharmony_ci			break;
1628c2ecf20Sopenharmony_ci		default:
1638c2ecf20Sopenharmony_ci			return -EINVAL;
1648c2ecf20Sopenharmony_ci		}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci		/* IIO_CONCENTRATION modifiers */
1678c2ecf20Sopenharmony_ci		switch (chan->channel2) {
1688c2ecf20Sopenharmony_ci		case IIO_MOD_CO2:
1698c2ecf20Sopenharmony_ci			*val = 0;
1708c2ecf20Sopenharmony_ci			*val2 = 100; /* 0.0001 */
1718c2ecf20Sopenharmony_ci			return IIO_VAL_INT_PLUS_MICRO;
1728c2ecf20Sopenharmony_ci		case IIO_MOD_O2:
1738c2ecf20Sopenharmony_ci			*val = 100;
1748c2ecf20Sopenharmony_ci			return IIO_VAL_INT;
1758c2ecf20Sopenharmony_ci		}
1768c2ecf20Sopenharmony_ci		return -EINVAL;
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	return 0;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic const struct iio_info atlas_info = {
1838c2ecf20Sopenharmony_ci	.read_raw = atlas_ezo_read_raw,
1848c2ecf20Sopenharmony_ci};
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic const struct i2c_device_id atlas_ezo_id[] = {
1878c2ecf20Sopenharmony_ci	{ "atlas-co2-ezo", ATLAS_CO2_EZO },
1888c2ecf20Sopenharmony_ci	{ "atlas-o2-ezo", ATLAS_O2_EZO },
1898c2ecf20Sopenharmony_ci	{ "atlas-hum-ezo", ATLAS_HUM_EZO },
1908c2ecf20Sopenharmony_ci	{}
1918c2ecf20Sopenharmony_ci};
1928c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, atlas_ezo_id);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cistatic const struct of_device_id atlas_ezo_dt_ids[] = {
1958c2ecf20Sopenharmony_ci	{ .compatible = "atlas,co2-ezo", .data = (void *)ATLAS_CO2_EZO, },
1968c2ecf20Sopenharmony_ci	{ .compatible = "atlas,o2-ezo", .data = (void *)ATLAS_O2_EZO, },
1978c2ecf20Sopenharmony_ci	{ .compatible = "atlas,hum-ezo", .data = (void *)ATLAS_HUM_EZO, },
1988c2ecf20Sopenharmony_ci	{}
1998c2ecf20Sopenharmony_ci};
2008c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic int atlas_ezo_probe(struct i2c_client *client,
2038c2ecf20Sopenharmony_ci		       const struct i2c_device_id *id)
2048c2ecf20Sopenharmony_ci{
2058c2ecf20Sopenharmony_ci	struct atlas_ezo_data *data;
2068c2ecf20Sopenharmony_ci	struct atlas_ezo_device *chip;
2078c2ecf20Sopenharmony_ci	const struct of_device_id *of_id;
2088c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
2118c2ecf20Sopenharmony_ci	if (!indio_dev)
2128c2ecf20Sopenharmony_ci		return -ENOMEM;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	of_id = of_match_device(atlas_ezo_dt_ids, &client->dev);
2158c2ecf20Sopenharmony_ci	if (!of_id)
2168c2ecf20Sopenharmony_ci		chip = &atlas_ezo_devices[id->driver_data];
2178c2ecf20Sopenharmony_ci	else
2188c2ecf20Sopenharmony_ci		chip = &atlas_ezo_devices[(unsigned long)of_id->data];
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	indio_dev->info = &atlas_info;
2218c2ecf20Sopenharmony_ci	indio_dev->name = ATLAS_EZO_DRV_NAME;
2228c2ecf20Sopenharmony_ci	indio_dev->channels = chip->channels;
2238c2ecf20Sopenharmony_ci	indio_dev->num_channels = chip->num_channels;
2248c2ecf20Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	data = iio_priv(indio_dev);
2278c2ecf20Sopenharmony_ci	data->client = client;
2288c2ecf20Sopenharmony_ci	data->chip = chip;
2298c2ecf20Sopenharmony_ci	mutex_init(&data->lock);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	return devm_iio_device_register(&client->dev, indio_dev);
2328c2ecf20Sopenharmony_ci};
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cistatic struct i2c_driver atlas_ezo_driver = {
2358c2ecf20Sopenharmony_ci	.driver = {
2368c2ecf20Sopenharmony_ci		.name	= ATLAS_EZO_DRV_NAME,
2378c2ecf20Sopenharmony_ci		.of_match_table	= atlas_ezo_dt_ids,
2388c2ecf20Sopenharmony_ci	},
2398c2ecf20Sopenharmony_ci	.probe		= atlas_ezo_probe,
2408c2ecf20Sopenharmony_ci	.id_table	= atlas_ezo_id,
2418c2ecf20Sopenharmony_ci};
2428c2ecf20Sopenharmony_cimodule_i2c_driver(atlas_ezo_driver);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
2458c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Atlas Scientific EZO sensors");
2468c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
247