summaryrefslogtreecommitdiffstats
path: root/drivers/iio/chemical/atlas-ezo-sensor.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/chemical/atlas-ezo-sensor.c')
-rw-r--r--drivers/iio/chemical/atlas-ezo-sensor.c246
1 files changed, 246 insertions, 0 deletions
diff --git a/drivers/iio/chemical/atlas-ezo-sensor.c b/drivers/iio/chemical/atlas-ezo-sensor.c
new file mode 100644
index 000000000..b1bacfe3c
--- /dev/null
+++ b/drivers/iio/chemical/atlas-ezo-sensor.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * atlas-ezo-sensor.c - Support for Atlas Scientific EZO sensors
+ *
+ * Copyright (C) 2020 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/iio/iio.h>
+
+#define ATLAS_EZO_DRV_NAME "atlas-ezo-sensor"
+#define ATLAS_INT_TIME_IN_MS 950
+#define ATLAS_INT_HUM_TIME_IN_MS 350
+
+enum {
+ ATLAS_CO2_EZO,
+ ATLAS_O2_EZO,
+ ATLAS_HUM_EZO,
+};
+
+struct atlas_ezo_device {
+ const struct iio_chan_spec *channels;
+ int num_channels;
+ int delay;
+};
+
+struct atlas_ezo_data {
+ struct i2c_client *client;
+ struct atlas_ezo_device *chip;
+
+ /* lock to avoid multiple concurrent read calls */
+ struct mutex lock;
+
+ u8 buffer[8];
+};
+
+#define ATLAS_CONCENTRATION_CHANNEL(_modifier) \
+ { \
+ .type = IIO_CONCENTRATION, \
+ .modified = 1,\
+ .channel2 = _modifier, \
+ .info_mask_separate = \
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = 0, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+ }
+
+static const struct iio_chan_spec atlas_co2_ezo_channels[] = {
+ ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_CO2),
+};
+
+static const struct iio_chan_spec atlas_o2_ezo_channels[] = {
+ ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_O2),
+};
+
+static const struct iio_chan_spec atlas_hum_ezo_channels[] = {
+ {
+ .type = IIO_HUMIDITYRELATIVE,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 32,
+ .storagebits = 32,
+ .endianness = IIO_CPU,
+ },
+ },
+};
+
+static struct atlas_ezo_device atlas_ezo_devices[] = {
+ [ATLAS_CO2_EZO] = {
+ .channels = atlas_co2_ezo_channels,
+ .num_channels = 1,
+ .delay = ATLAS_INT_TIME_IN_MS,
+ },
+ [ATLAS_O2_EZO] = {
+ .channels = atlas_o2_ezo_channels,
+ .num_channels = 1,
+ .delay = ATLAS_INT_TIME_IN_MS,
+ },
+ [ATLAS_HUM_EZO] = {
+ .channels = atlas_hum_ezo_channels,
+ .num_channels = 1,
+ .delay = ATLAS_INT_HUM_TIME_IN_MS,
+ },
+};
+
+static void atlas_ezo_sanitize(char *buf)
+{
+ char *ptr = strchr(buf, '.');
+
+ if (!ptr)
+ return;
+
+ memmove(ptr, ptr + 1, strlen(ptr));
+}
+
+static int atlas_ezo_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct atlas_ezo_data *data = iio_priv(indio_dev);
+ struct i2c_client *client = data->client;
+
+ if (chan->type != IIO_CONCENTRATION)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ int ret;
+ long tmp;
+
+ mutex_lock(&data->lock);
+
+ tmp = i2c_smbus_write_byte(client, 'R');
+
+ if (tmp < 0) {
+ mutex_unlock(&data->lock);
+ return tmp;
+ }
+
+ msleep(data->chip->delay);
+
+ tmp = i2c_master_recv(client, data->buffer, sizeof(data->buffer));
+
+ if (tmp < 0 || data->buffer[0] != 1) {
+ mutex_unlock(&data->lock);
+ return -EBUSY;
+ }
+
+ /* removing floating point for fixed number representation */
+ atlas_ezo_sanitize(data->buffer + 2);
+
+ ret = kstrtol(data->buffer + 1, 10, &tmp);
+
+ *val = tmp;
+
+ mutex_unlock(&data->lock);
+
+ return ret ? ret : IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_HUMIDITYRELATIVE:
+ *val = 10;
+ return IIO_VAL_INT;
+ case IIO_CONCENTRATION:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* IIO_CONCENTRATION modifiers */
+ switch (chan->channel2) {
+ case IIO_MOD_CO2:
+ *val = 0;
+ *val2 = 100; /* 0.0001 */
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_MOD_O2:
+ *val = 100;
+ return IIO_VAL_INT;
+ }
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct iio_info atlas_info = {
+ .read_raw = atlas_ezo_read_raw,
+};
+
+static const struct i2c_device_id atlas_ezo_id[] = {
+ { "atlas-co2-ezo", ATLAS_CO2_EZO },
+ { "atlas-o2-ezo", ATLAS_O2_EZO },
+ { "atlas-hum-ezo", ATLAS_HUM_EZO },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, atlas_ezo_id);
+
+static const struct of_device_id atlas_ezo_dt_ids[] = {
+ { .compatible = "atlas,co2-ezo", .data = (void *)ATLAS_CO2_EZO, },
+ { .compatible = "atlas,o2-ezo", .data = (void *)ATLAS_O2_EZO, },
+ { .compatible = "atlas,hum-ezo", .data = (void *)ATLAS_HUM_EZO, },
+ {}
+};
+MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids);
+
+static int atlas_ezo_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct atlas_ezo_data *data;
+ struct atlas_ezo_device *chip;
+ const struct of_device_id *of_id;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ of_id = of_match_device(atlas_ezo_dt_ids, &client->dev);
+ if (!of_id)
+ chip = &atlas_ezo_devices[id->driver_data];
+ else
+ chip = &atlas_ezo_devices[(unsigned long)of_id->data];
+
+ indio_dev->info = &atlas_info;
+ indio_dev->name = ATLAS_EZO_DRV_NAME;
+ indio_dev->channels = chip->channels;
+ indio_dev->num_channels = chip->num_channels;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ data->chip = chip;
+ mutex_init(&data->lock);
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+};
+
+static struct i2c_driver atlas_ezo_driver = {
+ .driver = {
+ .name = ATLAS_EZO_DRV_NAME,
+ .of_match_table = atlas_ezo_dt_ids,
+ },
+ .probe = atlas_ezo_probe,
+ .id_table = atlas_ezo_id,
+};
+module_i2c_driver(atlas_ezo_driver);
+
+MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
+MODULE_DESCRIPTION("Atlas Scientific EZO sensors");
+MODULE_LICENSE("GPL");