18712b309SMatt Ranostay // SPDX-License-Identifier: GPL-2.0+ 28712b309SMatt Ranostay /* 38712b309SMatt Ranostay * atlas-ezo-sensor.c - Support for Atlas Scientific EZO sensors 48712b309SMatt Ranostay * 58712b309SMatt Ranostay * Copyright (C) 2020 Konsulko Group 68712b309SMatt Ranostay * Author: Matt Ranostay <matt.ranostay@konsulko.com> 78712b309SMatt Ranostay */ 88712b309SMatt Ranostay 98712b309SMatt Ranostay #include <linux/module.h> 108712b309SMatt Ranostay #include <linux/init.h> 118712b309SMatt Ranostay #include <linux/delay.h> 128712b309SMatt Ranostay #include <linux/mutex.h> 138712b309SMatt Ranostay #include <linux/err.h> 148712b309SMatt Ranostay #include <linux/i2c.h> 158712b309SMatt Ranostay #include <linux/of_device.h> 168712b309SMatt Ranostay #include <linux/iio/iio.h> 178712b309SMatt Ranostay 188712b309SMatt Ranostay #define ATLAS_EZO_DRV_NAME "atlas-ezo-sensor" 196da3a6ceSMatt Ranostay #define ATLAS_INT_TIME_IN_MS 950 20*dc3ebfcaSMatt Ranostay #define ATLAS_INT_HUM_TIME_IN_MS 350 218712b309SMatt Ranostay 228712b309SMatt Ranostay enum { 238712b309SMatt Ranostay ATLAS_CO2_EZO, 246da3a6ceSMatt Ranostay ATLAS_O2_EZO, 25*dc3ebfcaSMatt Ranostay ATLAS_HUM_EZO, 268712b309SMatt Ranostay }; 278712b309SMatt Ranostay 288712b309SMatt Ranostay struct atlas_ezo_device { 298712b309SMatt Ranostay const struct iio_chan_spec *channels; 308712b309SMatt Ranostay int num_channels; 318712b309SMatt Ranostay int delay; 328712b309SMatt Ranostay }; 338712b309SMatt Ranostay 348712b309SMatt Ranostay struct atlas_ezo_data { 358712b309SMatt Ranostay struct i2c_client *client; 368712b309SMatt Ranostay struct atlas_ezo_device *chip; 378712b309SMatt Ranostay 388712b309SMatt Ranostay /* lock to avoid multiple concurrent read calls */ 398712b309SMatt Ranostay struct mutex lock; 408712b309SMatt Ranostay 418712b309SMatt Ranostay u8 buffer[8]; 428712b309SMatt Ranostay }; 438712b309SMatt Ranostay 446da3a6ceSMatt Ranostay #define ATLAS_CONCENTRATION_CHANNEL(_modifier) \ 456da3a6ceSMatt Ranostay { \ 466da3a6ceSMatt Ranostay .type = IIO_CONCENTRATION, \ 476da3a6ceSMatt Ranostay .modified = 1,\ 486da3a6ceSMatt Ranostay .channel2 = _modifier, \ 496da3a6ceSMatt Ranostay .info_mask_separate = \ 506da3a6ceSMatt Ranostay BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ 516da3a6ceSMatt Ranostay .scan_index = 0, \ 526da3a6ceSMatt Ranostay .scan_type = { \ 536da3a6ceSMatt Ranostay .sign = 'u', \ 546da3a6ceSMatt Ranostay .realbits = 32, \ 556da3a6ceSMatt Ranostay .storagebits = 32, \ 566da3a6ceSMatt Ranostay .endianness = IIO_CPU, \ 576da3a6ceSMatt Ranostay }, \ 586da3a6ceSMatt Ranostay } 596da3a6ceSMatt Ranostay 608712b309SMatt Ranostay static const struct iio_chan_spec atlas_co2_ezo_channels[] = { 616da3a6ceSMatt Ranostay ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_CO2), 626da3a6ceSMatt Ranostay }; 636da3a6ceSMatt Ranostay 646da3a6ceSMatt Ranostay static const struct iio_chan_spec atlas_o2_ezo_channels[] = { 656da3a6ceSMatt Ranostay ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_O2), 668712b309SMatt Ranostay }; 678712b309SMatt Ranostay 68*dc3ebfcaSMatt Ranostay static const struct iio_chan_spec atlas_hum_ezo_channels[] = { 69*dc3ebfcaSMatt Ranostay { 70*dc3ebfcaSMatt Ranostay .type = IIO_HUMIDITYRELATIVE, 71*dc3ebfcaSMatt Ranostay .info_mask_separate = 72*dc3ebfcaSMatt Ranostay BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), 73*dc3ebfcaSMatt Ranostay .scan_index = 0, 74*dc3ebfcaSMatt Ranostay .scan_type = { 75*dc3ebfcaSMatt Ranostay .sign = 'u', 76*dc3ebfcaSMatt Ranostay .realbits = 32, 77*dc3ebfcaSMatt Ranostay .storagebits = 32, 78*dc3ebfcaSMatt Ranostay .endianness = IIO_CPU, 79*dc3ebfcaSMatt Ranostay }, 80*dc3ebfcaSMatt Ranostay }, 81*dc3ebfcaSMatt Ranostay }; 82*dc3ebfcaSMatt Ranostay 838712b309SMatt Ranostay static struct atlas_ezo_device atlas_ezo_devices[] = { 848712b309SMatt Ranostay [ATLAS_CO2_EZO] = { 858712b309SMatt Ranostay .channels = atlas_co2_ezo_channels, 868712b309SMatt Ranostay .num_channels = 1, 876da3a6ceSMatt Ranostay .delay = ATLAS_INT_TIME_IN_MS, 888712b309SMatt Ranostay }, 896da3a6ceSMatt Ranostay [ATLAS_O2_EZO] = { 906da3a6ceSMatt Ranostay .channels = atlas_o2_ezo_channels, 916da3a6ceSMatt Ranostay .num_channels = 1, 926da3a6ceSMatt Ranostay .delay = ATLAS_INT_TIME_IN_MS, 93*dc3ebfcaSMatt Ranostay }, 94*dc3ebfcaSMatt Ranostay [ATLAS_HUM_EZO] = { 95*dc3ebfcaSMatt Ranostay .channels = atlas_hum_ezo_channels, 96*dc3ebfcaSMatt Ranostay .num_channels = 1, 97*dc3ebfcaSMatt Ranostay .delay = ATLAS_INT_HUM_TIME_IN_MS, 98*dc3ebfcaSMatt Ranostay }, 998712b309SMatt Ranostay }; 1008712b309SMatt Ranostay 1016da3a6ceSMatt Ranostay static void atlas_ezo_sanitize(char *buf) 1026da3a6ceSMatt Ranostay { 1036da3a6ceSMatt Ranostay char *ptr = strchr(buf, '.'); 1046da3a6ceSMatt Ranostay 1056da3a6ceSMatt Ranostay if (!ptr) 1066da3a6ceSMatt Ranostay return; 1076da3a6ceSMatt Ranostay 1086da3a6ceSMatt Ranostay memmove(ptr, ptr + 1, strlen(ptr)); 1096da3a6ceSMatt Ranostay } 1106da3a6ceSMatt Ranostay 1118712b309SMatt Ranostay static int atlas_ezo_read_raw(struct iio_dev *indio_dev, 1128712b309SMatt Ranostay struct iio_chan_spec const *chan, 1138712b309SMatt Ranostay int *val, int *val2, long mask) 1148712b309SMatt Ranostay { 1158712b309SMatt Ranostay struct atlas_ezo_data *data = iio_priv(indio_dev); 1168712b309SMatt Ranostay struct i2c_client *client = data->client; 1178712b309SMatt Ranostay 1188712b309SMatt Ranostay if (chan->type != IIO_CONCENTRATION) 1198712b309SMatt Ranostay return -EINVAL; 1208712b309SMatt Ranostay 1218712b309SMatt Ranostay switch (mask) { 1228712b309SMatt Ranostay case IIO_CHAN_INFO_RAW: { 12368ba6eeeSMatt Ranostay int ret; 1248712b309SMatt Ranostay long tmp; 1258712b309SMatt Ranostay 1268712b309SMatt Ranostay mutex_lock(&data->lock); 1278712b309SMatt Ranostay 1288712b309SMatt Ranostay tmp = i2c_smbus_write_byte(client, 'R'); 1298712b309SMatt Ranostay 1308712b309SMatt Ranostay if (tmp < 0) { 1318712b309SMatt Ranostay mutex_unlock(&data->lock); 1328712b309SMatt Ranostay return tmp; 1338712b309SMatt Ranostay } 1348712b309SMatt Ranostay 1358712b309SMatt Ranostay msleep(data->chip->delay); 1368712b309SMatt Ranostay 1378712b309SMatt Ranostay tmp = i2c_master_recv(client, data->buffer, sizeof(data->buffer)); 1388712b309SMatt Ranostay 1398712b309SMatt Ranostay if (tmp < 0 || data->buffer[0] != 1) { 1408712b309SMatt Ranostay mutex_unlock(&data->lock); 1418712b309SMatt Ranostay return -EBUSY; 1428712b309SMatt Ranostay } 1438712b309SMatt Ranostay 1446da3a6ceSMatt Ranostay /* removing floating point for fixed number representation */ 1456da3a6ceSMatt Ranostay atlas_ezo_sanitize(data->buffer + 2); 1466da3a6ceSMatt Ranostay 1478712b309SMatt Ranostay ret = kstrtol(data->buffer + 1, 10, &tmp); 1488712b309SMatt Ranostay 1498712b309SMatt Ranostay *val = tmp; 1508712b309SMatt Ranostay 1518712b309SMatt Ranostay mutex_unlock(&data->lock); 1528712b309SMatt Ranostay 1538712b309SMatt Ranostay return ret ? ret : IIO_VAL_INT; 1548712b309SMatt Ranostay } 1558712b309SMatt Ranostay case IIO_CHAN_INFO_SCALE: 156*dc3ebfcaSMatt Ranostay switch (chan->type) { 157*dc3ebfcaSMatt Ranostay case IIO_HUMIDITYRELATIVE: 158*dc3ebfcaSMatt Ranostay *val = 10; 159*dc3ebfcaSMatt Ranostay return IIO_VAL_INT; 160*dc3ebfcaSMatt Ranostay case IIO_CONCENTRATION: 161*dc3ebfcaSMatt Ranostay break; 162*dc3ebfcaSMatt Ranostay default: 163*dc3ebfcaSMatt Ranostay return -EINVAL; 164*dc3ebfcaSMatt Ranostay } 165*dc3ebfcaSMatt Ranostay 166*dc3ebfcaSMatt Ranostay /* IIO_CONCENTRATION modifiers */ 1676da3a6ceSMatt Ranostay switch (chan->channel2) { 1686da3a6ceSMatt Ranostay case IIO_MOD_CO2: 1698712b309SMatt Ranostay *val = 0; 1708712b309SMatt Ranostay *val2 = 100; /* 0.0001 */ 1718712b309SMatt Ranostay return IIO_VAL_INT_PLUS_MICRO; 1726da3a6ceSMatt Ranostay case IIO_MOD_O2: 1736da3a6ceSMatt Ranostay *val = 100; 1746da3a6ceSMatt Ranostay return IIO_VAL_INT; 1756da3a6ceSMatt Ranostay } 1766da3a6ceSMatt Ranostay return -EINVAL; 1778712b309SMatt Ranostay } 1788712b309SMatt Ranostay 17968ba6eeeSMatt Ranostay return 0; 1808712b309SMatt Ranostay } 1818712b309SMatt Ranostay 1828712b309SMatt Ranostay static const struct iio_info atlas_info = { 1838712b309SMatt Ranostay .read_raw = atlas_ezo_read_raw, 1848712b309SMatt Ranostay }; 1858712b309SMatt Ranostay 1868712b309SMatt Ranostay static const struct i2c_device_id atlas_ezo_id[] = { 1878712b309SMatt Ranostay { "atlas-co2-ezo", ATLAS_CO2_EZO }, 1886da3a6ceSMatt Ranostay { "atlas-o2-ezo", ATLAS_O2_EZO }, 189*dc3ebfcaSMatt Ranostay { "atlas-hum-ezo", ATLAS_HUM_EZO }, 1908712b309SMatt Ranostay {} 1918712b309SMatt Ranostay }; 1928712b309SMatt Ranostay MODULE_DEVICE_TABLE(i2c, atlas_ezo_id); 1938712b309SMatt Ranostay 1948712b309SMatt Ranostay static const struct of_device_id atlas_ezo_dt_ids[] = { 1958712b309SMatt Ranostay { .compatible = "atlas,co2-ezo", .data = (void *)ATLAS_CO2_EZO, }, 1966da3a6ceSMatt Ranostay { .compatible = "atlas,o2-ezo", .data = (void *)ATLAS_O2_EZO, }, 197*dc3ebfcaSMatt Ranostay { .compatible = "atlas,hum-ezo", .data = (void *)ATLAS_HUM_EZO, }, 1988712b309SMatt Ranostay {} 1998712b309SMatt Ranostay }; 2008712b309SMatt Ranostay MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids); 2018712b309SMatt Ranostay 2028712b309SMatt Ranostay static int atlas_ezo_probe(struct i2c_client *client, 2038712b309SMatt Ranostay const struct i2c_device_id *id) 2048712b309SMatt Ranostay { 2058712b309SMatt Ranostay struct atlas_ezo_data *data; 2068712b309SMatt Ranostay struct atlas_ezo_device *chip; 2078712b309SMatt Ranostay const struct of_device_id *of_id; 2088712b309SMatt Ranostay struct iio_dev *indio_dev; 2098712b309SMatt Ranostay 2108712b309SMatt Ranostay indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); 2118712b309SMatt Ranostay if (!indio_dev) 2128712b309SMatt Ranostay return -ENOMEM; 2138712b309SMatt Ranostay 2148712b309SMatt Ranostay of_id = of_match_device(atlas_ezo_dt_ids, &client->dev); 2158712b309SMatt Ranostay if (!of_id) 2168712b309SMatt Ranostay chip = &atlas_ezo_devices[id->driver_data]; 2178712b309SMatt Ranostay else 2188712b309SMatt Ranostay chip = &atlas_ezo_devices[(unsigned long)of_id->data]; 2198712b309SMatt Ranostay 2208712b309SMatt Ranostay indio_dev->info = &atlas_info; 2218712b309SMatt Ranostay indio_dev->name = ATLAS_EZO_DRV_NAME; 2228712b309SMatt Ranostay indio_dev->channels = chip->channels; 2238712b309SMatt Ranostay indio_dev->num_channels = chip->num_channels; 2248712b309SMatt Ranostay indio_dev->modes = INDIO_DIRECT_MODE; 2258712b309SMatt Ranostay 2268712b309SMatt Ranostay data = iio_priv(indio_dev); 2278712b309SMatt Ranostay data->client = client; 2288712b309SMatt Ranostay data->chip = chip; 2298712b309SMatt Ranostay mutex_init(&data->lock); 2308712b309SMatt Ranostay 2318712b309SMatt Ranostay return devm_iio_device_register(&client->dev, indio_dev); 2328712b309SMatt Ranostay }; 2338712b309SMatt Ranostay 2348712b309SMatt Ranostay static struct i2c_driver atlas_ezo_driver = { 2358712b309SMatt Ranostay .driver = { 2368712b309SMatt Ranostay .name = ATLAS_EZO_DRV_NAME, 2378712b309SMatt Ranostay .of_match_table = atlas_ezo_dt_ids, 2388712b309SMatt Ranostay }, 2398712b309SMatt Ranostay .probe = atlas_ezo_probe, 2408712b309SMatt Ranostay .id_table = atlas_ezo_id, 2418712b309SMatt Ranostay }; 2428712b309SMatt Ranostay module_i2c_driver(atlas_ezo_driver); 2438712b309SMatt Ranostay 2448712b309SMatt Ranostay MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>"); 2458712b309SMatt Ranostay MODULE_DESCRIPTION("Atlas Scientific EZO sensors"); 2468712b309SMatt Ranostay MODULE_LICENSE("GPL"); 247