1 /* 2 * vz89x.c - Support for SGX Sensortech MiCS VZ89X 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 23 #include <linux/iio/iio.h> 24 #include <linux/iio/sysfs.h> 25 26 #define VZ89X_REG_MEASUREMENT 0x09 27 #define VZ89X_REG_MEASUREMENT_SIZE 6 28 29 #define VZ89X_VOC_CO2_IDX 0 30 #define VZ89X_VOC_SHORT_IDX 1 31 #define VZ89X_VOC_TVOC_IDX 2 32 #define VZ89X_VOC_RESISTANCE_IDX 3 33 34 struct vz89x_data { 35 struct i2c_client *client; 36 struct mutex lock; 37 int (*xfer)(struct vz89x_data *data, u8 cmd); 38 39 unsigned long last_update; 40 u8 buffer[VZ89X_REG_MEASUREMENT_SIZE]; 41 }; 42 43 static const struct iio_chan_spec vz89x_channels[] = { 44 { 45 .type = IIO_CONCENTRATION, 46 .channel2 = IIO_MOD_CO2, 47 .modified = 1, 48 .info_mask_separate = 49 BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), 50 .address = VZ89X_VOC_CO2_IDX, 51 }, 52 { 53 .type = IIO_CONCENTRATION, 54 .channel2 = IIO_MOD_VOC, 55 .modified = 1, 56 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 57 .address = VZ89X_VOC_SHORT_IDX, 58 .extend_name = "short", 59 }, 60 { 61 .type = IIO_CONCENTRATION, 62 .channel2 = IIO_MOD_VOC, 63 .modified = 1, 64 .info_mask_separate = 65 BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), 66 .address = VZ89X_VOC_TVOC_IDX, 67 }, 68 { 69 .type = IIO_RESISTANCE, 70 .info_mask_separate = 71 BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), 72 .address = VZ89X_VOC_RESISTANCE_IDX, 73 }, 74 }; 75 76 static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689"); 77 static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223"); 78 79 static struct attribute *vz89x_attributes[] = { 80 &iio_const_attr_in_concentration_co2_scale.dev_attr.attr, 81 &iio_const_attr_in_concentration_voc_scale.dev_attr.attr, 82 NULL, 83 }; 84 85 static const struct attribute_group vz89x_attrs_group = { 86 .attrs = vz89x_attributes, 87 }; 88 89 /* 90 * Chipset sometime updates in the middle of a reading causing it to reset the 91 * data pointer, and causing invalid reading of previous data. 92 * We can check for this by reading MSB of the resistance reading that is 93 * always zero, and by also confirming the VOC_short isn't zero. 94 */ 95 96 static int vz89x_measurement_is_valid(struct vz89x_data *data) 97 { 98 if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0) 99 return 1; 100 101 return !!(data->buffer[VZ89X_REG_MEASUREMENT_SIZE - 1] > 0); 102 } 103 104 static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd) 105 { 106 struct i2c_client *client = data->client; 107 struct i2c_msg msg[2]; 108 int ret; 109 u8 buf[3] = { cmd, 0, 0}; 110 111 msg[0].addr = client->addr; 112 msg[0].flags = client->flags; 113 msg[0].len = 3; 114 msg[0].buf = (char *) &buf; 115 116 msg[1].addr = client->addr; 117 msg[1].flags = client->flags | I2C_M_RD; 118 msg[1].len = VZ89X_REG_MEASUREMENT_SIZE; 119 msg[1].buf = (char *) &data->buffer; 120 121 ret = i2c_transfer(client->adapter, msg, 2); 122 123 return (ret == 2) ? 0 : ret; 124 } 125 126 static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd) 127 { 128 struct i2c_client *client = data->client; 129 int ret; 130 int i; 131 132 ret = i2c_smbus_write_word_data(client, cmd, 0); 133 if (ret < 0) 134 return ret; 135 136 for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) { 137 ret = i2c_smbus_read_byte(client); 138 if (ret < 0) 139 return ret; 140 data->buffer[i] = ret; 141 } 142 143 return 0; 144 } 145 146 static int vz89x_get_measurement(struct vz89x_data *data) 147 { 148 int ret; 149 150 /* sensor can only be polled once a second max per datasheet */ 151 if (!time_after(jiffies, data->last_update + HZ)) 152 return 0; 153 154 ret = data->xfer(data, VZ89X_REG_MEASUREMENT); 155 if (ret < 0) 156 return ret; 157 158 ret = vz89x_measurement_is_valid(data); 159 if (ret) 160 return -EAGAIN; 161 162 data->last_update = jiffies; 163 164 return 0; 165 } 166 167 static int vz89x_get_resistance_reading(struct vz89x_data *data) 168 { 169 u8 *buf = &data->buffer[VZ89X_VOC_RESISTANCE_IDX]; 170 171 return buf[0] | (buf[1] << 8); 172 } 173 174 static int vz89x_read_raw(struct iio_dev *indio_dev, 175 struct iio_chan_spec const *chan, int *val, 176 int *val2, long mask) 177 { 178 struct vz89x_data *data = iio_priv(indio_dev); 179 int ret = -EINVAL; 180 181 switch (mask) { 182 case IIO_CHAN_INFO_RAW: 183 mutex_lock(&data->lock); 184 ret = vz89x_get_measurement(data); 185 mutex_unlock(&data->lock); 186 187 if (ret) 188 return ret; 189 190 switch (chan->address) { 191 case VZ89X_VOC_CO2_IDX: 192 case VZ89X_VOC_SHORT_IDX: 193 case VZ89X_VOC_TVOC_IDX: 194 *val = data->buffer[chan->address]; 195 return IIO_VAL_INT; 196 case VZ89X_VOC_RESISTANCE_IDX: 197 *val = vz89x_get_resistance_reading(data); 198 return IIO_VAL_INT; 199 default: 200 return -EINVAL; 201 } 202 break; 203 case IIO_CHAN_INFO_SCALE: 204 switch (chan->type) { 205 case IIO_RESISTANCE: 206 *val = 10; 207 return IIO_VAL_INT; 208 default: 209 return -EINVAL; 210 } 211 break; 212 case IIO_CHAN_INFO_OFFSET: 213 switch (chan->address) { 214 case VZ89X_VOC_CO2_IDX: 215 *val = 44; 216 *val2 = 250000; 217 return IIO_VAL_INT_PLUS_MICRO; 218 case VZ89X_VOC_TVOC_IDX: 219 *val = -13; 220 return IIO_VAL_INT; 221 default: 222 return -EINVAL; 223 } 224 } 225 226 return ret; 227 } 228 229 static const struct iio_info vz89x_info = { 230 .attrs = &vz89x_attrs_group, 231 .read_raw = vz89x_read_raw, 232 .driver_module = THIS_MODULE, 233 }; 234 235 static int vz89x_probe(struct i2c_client *client, 236 const struct i2c_device_id *id) 237 { 238 struct iio_dev *indio_dev; 239 struct vz89x_data *data; 240 241 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); 242 if (!indio_dev) 243 return -ENOMEM; 244 data = iio_priv(indio_dev); 245 246 if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) 247 data->xfer = vz89x_i2c_xfer; 248 else if (i2c_check_functionality(client->adapter, 249 I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) 250 data->xfer = vz89x_smbus_xfer; 251 else 252 return -EOPNOTSUPP; 253 254 i2c_set_clientdata(client, indio_dev); 255 data->client = client; 256 data->last_update = jiffies - HZ; 257 mutex_init(&data->lock); 258 259 indio_dev->dev.parent = &client->dev; 260 indio_dev->info = &vz89x_info, 261 indio_dev->name = dev_name(&client->dev); 262 indio_dev->modes = INDIO_DIRECT_MODE; 263 264 indio_dev->channels = vz89x_channels; 265 indio_dev->num_channels = ARRAY_SIZE(vz89x_channels); 266 267 return devm_iio_device_register(&client->dev, indio_dev); 268 } 269 270 static const struct i2c_device_id vz89x_id[] = { 271 { "vz89x", 0 }, 272 { } 273 }; 274 MODULE_DEVICE_TABLE(i2c, vz89x_id); 275 276 static const struct of_device_id vz89x_dt_ids[] = { 277 { .compatible = "sgx,vz89x" }, 278 { } 279 }; 280 MODULE_DEVICE_TABLE(of, vz89x_dt_ids); 281 282 static struct i2c_driver vz89x_driver = { 283 .driver = { 284 .name = "vz89x", 285 .of_match_table = of_match_ptr(vz89x_dt_ids), 286 }, 287 .probe = vz89x_probe, 288 .id_table = vz89x_id, 289 }; 290 module_i2c_driver(vz89x_driver); 291 292 MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>"); 293 MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors"); 294 MODULE_LICENSE("GPL v2"); 295