1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors 4 * 5 * Copyright (C) 2015, 2018 6 * Author: Matt Ranostay <matt.ranostay@konsulko.com> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/mod_devicetable.h> 11 #include <linux/mutex.h> 12 #include <linux/init.h> 13 #include <linux/i2c.h> 14 #include <linux/iio/iio.h> 15 16 #define AMS_IAQCORE_DATA_SIZE 9 17 18 #define AMS_IAQCORE_VOC_CO2_IDX 0 19 #define AMS_IAQCORE_VOC_RESISTANCE_IDX 1 20 #define AMS_IAQCORE_VOC_TVOC_IDX 2 21 22 struct ams_iaqcore_reading { 23 __be16 co2_ppm; 24 u8 status; 25 __be32 resistance; 26 __be16 voc_ppb; 27 } __attribute__((__packed__)); 28 29 struct ams_iaqcore_data { 30 struct i2c_client *client; 31 struct mutex lock; 32 unsigned long last_update; 33 34 struct ams_iaqcore_reading buffer; 35 }; 36 37 static const struct iio_chan_spec ams_iaqcore_channels[] = { 38 { 39 .type = IIO_CONCENTRATION, 40 .channel2 = IIO_MOD_CO2, 41 .modified = 1, 42 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), 43 .address = AMS_IAQCORE_VOC_CO2_IDX, 44 }, 45 { 46 .type = IIO_RESISTANCE, 47 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), 48 .address = AMS_IAQCORE_VOC_RESISTANCE_IDX, 49 }, 50 { 51 .type = IIO_CONCENTRATION, 52 .channel2 = IIO_MOD_VOC, 53 .modified = 1, 54 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), 55 .address = AMS_IAQCORE_VOC_TVOC_IDX, 56 }, 57 }; 58 59 static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data) 60 { 61 struct i2c_client *client = data->client; 62 int ret; 63 64 struct i2c_msg msg = { 65 .addr = client->addr, 66 .flags = client->flags | I2C_M_RD, 67 .len = AMS_IAQCORE_DATA_SIZE, 68 .buf = (char *) &data->buffer, 69 }; 70 71 ret = i2c_transfer(client->adapter, &msg, 1); 72 73 return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret; 74 } 75 76 static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data) 77 { 78 int ret; 79 80 /* sensor can only be polled once a second max per datasheet */ 81 if (!time_after(jiffies, data->last_update + HZ)) 82 return 0; 83 84 ret = ams_iaqcore_read_measurement(data); 85 if (ret < 0) 86 return ret; 87 88 data->last_update = jiffies; 89 90 return 0; 91 } 92 93 static int ams_iaqcore_read_raw(struct iio_dev *indio_dev, 94 struct iio_chan_spec const *chan, int *val, 95 int *val2, long mask) 96 { 97 struct ams_iaqcore_data *data = iio_priv(indio_dev); 98 int ret; 99 100 if (mask != IIO_CHAN_INFO_PROCESSED) 101 return -EINVAL; 102 103 mutex_lock(&data->lock); 104 ret = ams_iaqcore_get_measurement(data); 105 106 if (ret) 107 goto err_out; 108 109 switch (chan->address) { 110 case AMS_IAQCORE_VOC_CO2_IDX: 111 *val = 0; 112 *val2 = be16_to_cpu(data->buffer.co2_ppm); 113 ret = IIO_VAL_INT_PLUS_MICRO; 114 break; 115 case AMS_IAQCORE_VOC_RESISTANCE_IDX: 116 *val = be32_to_cpu(data->buffer.resistance); 117 ret = IIO_VAL_INT; 118 break; 119 case AMS_IAQCORE_VOC_TVOC_IDX: 120 *val = 0; 121 *val2 = be16_to_cpu(data->buffer.voc_ppb); 122 ret = IIO_VAL_INT_PLUS_NANO; 123 break; 124 default: 125 ret = -EINVAL; 126 } 127 128 err_out: 129 mutex_unlock(&data->lock); 130 131 return ret; 132 } 133 134 static const struct iio_info ams_iaqcore_info = { 135 .read_raw = ams_iaqcore_read_raw, 136 }; 137 138 static int ams_iaqcore_probe(struct i2c_client *client) 139 { 140 struct iio_dev *indio_dev; 141 struct ams_iaqcore_data *data; 142 143 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); 144 if (!indio_dev) 145 return -ENOMEM; 146 147 data = iio_priv(indio_dev); 148 i2c_set_clientdata(client, indio_dev); 149 data->client = client; 150 151 /* so initial reading will complete */ 152 data->last_update = jiffies - HZ; 153 mutex_init(&data->lock); 154 155 indio_dev->info = &ams_iaqcore_info; 156 indio_dev->name = dev_name(&client->dev); 157 indio_dev->modes = INDIO_DIRECT_MODE; 158 159 indio_dev->channels = ams_iaqcore_channels; 160 indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels); 161 162 return devm_iio_device_register(&client->dev, indio_dev); 163 } 164 165 static const struct i2c_device_id ams_iaqcore_id[] = { 166 { "ams-iaq-core", 0 }, 167 { } 168 }; 169 MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id); 170 171 static const struct of_device_id ams_iaqcore_dt_ids[] = { 172 { .compatible = "ams,iaq-core" }, 173 { } 174 }; 175 MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids); 176 177 static struct i2c_driver ams_iaqcore_driver = { 178 .driver = { 179 .name = "ams-iaq-core", 180 .of_match_table = ams_iaqcore_dt_ids, 181 }, 182 .probe_new = ams_iaqcore_probe, 183 .id_table = ams_iaqcore_id, 184 }; 185 module_i2c_driver(ams_iaqcore_driver); 186 187 MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>"); 188 MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors"); 189 MODULE_LICENSE("GPL v2"); 190