245 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * atlas-ezo-sensor.c - Support for Atlas Scientific EZO sensors4 *5 * Copyright (C) 2020 Konsulko Group6 * Author: Matt Ranostay <matt.ranostay@konsulko.com>7 */8 9#include <linux/init.h>10#include <linux/delay.h>11#include <linux/mod_devicetable.h>12#include <linux/module.h>13#include <linux/mutex.h>14#include <linux/property.h>15#include <linux/err.h>16#include <linux/i2c.h>17 18#include <linux/iio/iio.h>19 20#define ATLAS_EZO_DRV_NAME "atlas-ezo-sensor"21#define ATLAS_INT_TIME_IN_MS 95022#define ATLAS_INT_HUM_TIME_IN_MS 35023 24enum {25 ATLAS_CO2_EZO,26 ATLAS_O2_EZO,27 ATLAS_HUM_EZO,28};29 30struct atlas_ezo_device {31 const struct iio_chan_spec *channels;32 int num_channels;33 int delay;34};35 36struct atlas_ezo_data {37 struct i2c_client *client;38 const struct atlas_ezo_device *chip;39 40 /* lock to avoid multiple concurrent read calls */41 struct mutex lock;42 43 u8 buffer[8];44};45 46#define ATLAS_CONCENTRATION_CHANNEL(_modifier) \47 { \48 .type = IIO_CONCENTRATION, \49 .modified = 1,\50 .channel2 = _modifier, \51 .info_mask_separate = \52 BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \53 .scan_index = 0, \54 .scan_type = { \55 .sign = 'u', \56 .realbits = 32, \57 .storagebits = 32, \58 .endianness = IIO_CPU, \59 }, \60 }61 62static const struct iio_chan_spec atlas_co2_ezo_channels[] = {63 ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_CO2),64};65 66static const struct iio_chan_spec atlas_o2_ezo_channels[] = {67 ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_O2),68};69 70static const struct iio_chan_spec atlas_hum_ezo_channels[] = {71 {72 .type = IIO_HUMIDITYRELATIVE,73 .info_mask_separate =74 BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),75 .scan_index = 0,76 .scan_type = {77 .sign = 'u',78 .realbits = 32,79 .storagebits = 32,80 .endianness = IIO_CPU,81 },82 },83};84 85static struct atlas_ezo_device atlas_ezo_devices[] = {86 [ATLAS_CO2_EZO] = {87 .channels = atlas_co2_ezo_channels,88 .num_channels = 1,89 .delay = ATLAS_INT_TIME_IN_MS,90 },91 [ATLAS_O2_EZO] = {92 .channels = atlas_o2_ezo_channels,93 .num_channels = 1,94 .delay = ATLAS_INT_TIME_IN_MS,95 },96 [ATLAS_HUM_EZO] = {97 .channels = atlas_hum_ezo_channels,98 .num_channels = 1,99 .delay = ATLAS_INT_HUM_TIME_IN_MS,100 },101};102 103static void atlas_ezo_sanitize(char *buf)104{105 char *ptr = strchr(buf, '.');106 107 if (!ptr)108 return;109 110 memmove(ptr, ptr + 1, strlen(ptr));111}112 113static int atlas_ezo_read_raw(struct iio_dev *indio_dev,114 struct iio_chan_spec const *chan,115 int *val, int *val2, long mask)116{117 struct atlas_ezo_data *data = iio_priv(indio_dev);118 struct i2c_client *client = data->client;119 120 if (chan->type != IIO_CONCENTRATION)121 return -EINVAL;122 123 switch (mask) {124 case IIO_CHAN_INFO_RAW: {125 int ret;126 long tmp;127 128 mutex_lock(&data->lock);129 130 tmp = i2c_smbus_write_byte(client, 'R');131 132 if (tmp < 0) {133 mutex_unlock(&data->lock);134 return tmp;135 }136 137 msleep(data->chip->delay);138 139 tmp = i2c_master_recv(client, data->buffer, sizeof(data->buffer));140 141 if (tmp < 0 || data->buffer[0] != 1) {142 mutex_unlock(&data->lock);143 return -EBUSY;144 }145 146 /* removing floating point for fixed number representation */147 atlas_ezo_sanitize(data->buffer + 2);148 149 ret = kstrtol(data->buffer + 1, 10, &tmp);150 151 *val = tmp;152 153 mutex_unlock(&data->lock);154 155 return ret ? ret : IIO_VAL_INT;156 }157 case IIO_CHAN_INFO_SCALE:158 switch (chan->type) {159 case IIO_HUMIDITYRELATIVE:160 *val = 10;161 return IIO_VAL_INT;162 case IIO_CONCENTRATION:163 break;164 default:165 return -EINVAL;166 }167 168 /* IIO_CONCENTRATION modifiers */169 switch (chan->channel2) {170 case IIO_MOD_CO2:171 *val = 0;172 *val2 = 100; /* 0.0001 */173 return IIO_VAL_INT_PLUS_MICRO;174 case IIO_MOD_O2:175 *val = 100;176 return IIO_VAL_INT;177 }178 return -EINVAL;179 }180 181 return 0;182}183 184static const struct iio_info atlas_info = {185 .read_raw = atlas_ezo_read_raw,186};187 188static const struct i2c_device_id atlas_ezo_id[] = {189 { "atlas-co2-ezo", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_CO2_EZO] },190 { "atlas-o2-ezo", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_O2_EZO] },191 { "atlas-hum-ezo", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_HUM_EZO] },192 {}193};194MODULE_DEVICE_TABLE(i2c, atlas_ezo_id);195 196static const struct of_device_id atlas_ezo_dt_ids[] = {197 { .compatible = "atlas,co2-ezo", .data = &atlas_ezo_devices[ATLAS_CO2_EZO], },198 { .compatible = "atlas,o2-ezo", .data = &atlas_ezo_devices[ATLAS_O2_EZO], },199 { .compatible = "atlas,hum-ezo", .data = &atlas_ezo_devices[ATLAS_HUM_EZO], },200 {}201};202MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids);203 204static int atlas_ezo_probe(struct i2c_client *client)205{206 const struct atlas_ezo_device *chip;207 struct atlas_ezo_data *data;208 struct iio_dev *indio_dev;209 210 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));211 if (!indio_dev)212 return -ENOMEM;213 214 chip = i2c_get_match_data(client);215 if (!chip)216 return -EINVAL;217 218 indio_dev->info = &atlas_info;219 indio_dev->name = ATLAS_EZO_DRV_NAME;220 indio_dev->channels = chip->channels;221 indio_dev->num_channels = chip->num_channels;222 indio_dev->modes = INDIO_DIRECT_MODE;223 224 data = iio_priv(indio_dev);225 data->client = client;226 data->chip = chip;227 mutex_init(&data->lock);228 229 return devm_iio_device_register(&client->dev, indio_dev);230};231 232static struct i2c_driver atlas_ezo_driver = {233 .driver = {234 .name = ATLAS_EZO_DRV_NAME,235 .of_match_table = atlas_ezo_dt_ids,236 },237 .probe = atlas_ezo_probe,238 .id_table = atlas_ezo_id,239};240module_i2c_driver(atlas_ezo_driver);241 242MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");243MODULE_DESCRIPTION("Atlas Scientific EZO sensors");244MODULE_LICENSE("GPL");245