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