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/mutex.h> 11 #include <linux/init.h> 12 #include <linux/i2c.h> 13 #include <linux/iio/iio.h> 14 15 #define AMS_IAQCORE_DATA_SIZE 9 16 17 #define AMS_IAQCORE_VOC_CO2_IDX 0 18 #define AMS_IAQCORE_VOC_RESISTANCE_IDX 1 19 #define AMS_IAQCORE_VOC_TVOC_IDX 2 20 21 struct ams_iaqcore_reading { 22 __be16 co2_ppm; 23 u8 status; 24 __be32 resistance; 25 __be16 voc_ppb; 26 } __attribute__((__packed__)); 27 28 struct ams_iaqcore_data { 29 struct i2c_client *client; 30 struct mutex lock; 31 unsigned long last_update; 32 33 struct ams_iaqcore_reading buffer; 34 }; 35 36 static const struct iio_chan_spec ams_iaqcore_channels[] = { 37 { 38 .type = IIO_CONCENTRATION, 39 .channel2 = IIO_MOD_CO2, 40 .modified = 1, 41 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), 42 .address = AMS_IAQCORE_VOC_CO2_IDX, 43 }, 44 { 45 .type = IIO_RESISTANCE, 46 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), 47 .address = AMS_IAQCORE_VOC_RESISTANCE_IDX, 48 }, 49 { 50 .type = IIO_CONCENTRATION, 51 .channel2 = IIO_MOD_VOC, 52 .modified = 1, 53 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), 54 .address = AMS_IAQCORE_VOC_TVOC_IDX, 55 }, 56 }; 57 58 static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data) 59 { 60 struct i2c_client *client = data->client; 61 int ret; 62 63 struct i2c_msg msg = { 64 .addr = client->addr, 65 .flags = client->flags | I2C_M_RD, 66 .len = AMS_IAQCORE_DATA_SIZE, 67 .buf = (char *) &data->buffer, 68 }; 69 70 ret = i2c_transfer(client->adapter, &msg, 1); 71 72 return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret; 73 } 74 75 static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data) 76 { 77 int ret; 78 79 /* sensor can only be polled once a second max per datasheet */ 80 if (!time_after(jiffies, data->last_update + HZ)) 81 return 0; 82 83 ret = ams_iaqcore_read_measurement(data); 84 if (ret < 0) 85 return ret; 86 87 data->last_update = jiffies; 88 89 return 0; 90 } 91 92 static int ams_iaqcore_read_raw(struct iio_dev *indio_dev, 93 struct iio_chan_spec const *chan, int *val, 94 int *val2, long mask) 95 { 96 struct ams_iaqcore_data *data = iio_priv(indio_dev); 97 int ret; 98 99 if (mask != IIO_CHAN_INFO_PROCESSED) 100 return -EINVAL; 101 102 mutex_lock(&data->lock); 103 ret = ams_iaqcore_get_measurement(data); 104 105 if (ret) 106 goto err_out; 107 108 switch (chan->address) { 109 case AMS_IAQCORE_VOC_CO2_IDX: 110 *val = 0; 111 *val2 = be16_to_cpu(data->buffer.co2_ppm); 112 ret = IIO_VAL_INT_PLUS_MICRO; 113 break; 114 case AMS_IAQCORE_VOC_RESISTANCE_IDX: 115 *val = be32_to_cpu(data->buffer.resistance); 116 ret = IIO_VAL_INT; 117 break; 118 case AMS_IAQCORE_VOC_TVOC_IDX: 119 *val = 0; 120 *val2 = be16_to_cpu(data->buffer.voc_ppb); 121 ret = IIO_VAL_INT_PLUS_NANO; 122 break; 123 default: 124 ret = -EINVAL; 125 } 126 127 err_out: 128 mutex_unlock(&data->lock); 129 130 return ret; 131 } 132 133 static const struct iio_info ams_iaqcore_info = { 134 .read_raw = ams_iaqcore_read_raw, 135 }; 136 137 static int ams_iaqcore_probe(struct i2c_client *client, 138 const struct i2c_device_id *id) 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 = of_match_ptr(ams_iaqcore_dt_ids), 181 }, 182 .probe = 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