| // 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/init.h> |
| #include <linux/delay.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/property.h> |
| #include <linux/err.h> |
| #include <linux/i2c.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; |
| const 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", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_CO2_EZO] }, |
| { "atlas-o2-ezo", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_O2_EZO] }, |
| { "atlas-hum-ezo", (kernel_ulong_t)&atlas_ezo_devices[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 = &atlas_ezo_devices[ATLAS_CO2_EZO], }, |
| { .compatible = "atlas,o2-ezo", .data = &atlas_ezo_devices[ATLAS_O2_EZO], }, |
| { .compatible = "atlas,hum-ezo", .data = &atlas_ezo_devices[ATLAS_HUM_EZO], }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids); |
| |
| static int atlas_ezo_probe(struct i2c_client *client) |
| { |
| const struct atlas_ezo_device *chip; |
| struct atlas_ezo_data *data; |
| struct iio_dev *indio_dev; |
| |
| indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| chip = i2c_get_match_data(client); |
| if (!chip) |
| return -EINVAL; |
| |
| 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"); |