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