blob: aa6ebc084e1919d9a94c09406e77eb34254b24a8 [file] [log] [blame]
Matt Ranostaycd8d97772015-09-13 20:26:14 -07001/*
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>
Matt Ranostay83768822016-08-24 23:44:47 -070022#include <linux/of.h>
23#include <linux/of_device.h>
Matt Ranostaycd8d97772015-09-13 20:26:14 -070024
25#include <linux/iio/iio.h>
26#include <linux/iio/sysfs.h>
27
28#define VZ89X_REG_MEASUREMENT 0x09
Matt Ranostay83768822016-08-24 23:44:47 -070029#define VZ89X_REG_MEASUREMENT_RD_SIZE 6
30#define VZ89X_REG_MEASUREMENT_WR_SIZE 3
Matt Ranostaycd8d97772015-09-13 20:26:14 -070031
32#define VZ89X_VOC_CO2_IDX 0
33#define VZ89X_VOC_SHORT_IDX 1
34#define VZ89X_VOC_TVOC_IDX 2
35#define VZ89X_VOC_RESISTANCE_IDX 3
36
Matt Ranostay83768822016-08-24 23:44:47 -070037enum {
38 VZ89X,
39};
40
41struct vz89x_chip_data;
42
Matt Ranostaycd8d97772015-09-13 20:26:14 -070043struct vz89x_data {
44 struct i2c_client *client;
Matt Ranostay83768822016-08-24 23:44:47 -070045 const struct vz89x_chip_data *chip;
Matt Ranostaycd8d97772015-09-13 20:26:14 -070046 struct mutex lock;
Matt Ranostay93e87d72015-12-01 21:47:20 -080047 int (*xfer)(struct vz89x_data *data, u8 cmd);
Matt Ranostaycd8d97772015-09-13 20:26:14 -070048
Matt Ranostay93e87d72015-12-01 21:47:20 -080049 unsigned long last_update;
Matt Ranostay83768822016-08-24 23:44:47 -070050 u8 buffer[VZ89X_REG_MEASUREMENT_RD_SIZE];
51};
52
53struct vz89x_chip_data {
54 bool (*valid)(struct vz89x_data *data);
55 const struct iio_chan_spec *channels;
56 u8 num_channels;
57
58 u8 cmd;
59 u8 read_size;
60 u8 write_size;
Matt Ranostaycd8d97772015-09-13 20:26:14 -070061};
62
63static const struct iio_chan_spec vz89x_channels[] = {
64 {
65 .type = IIO_CONCENTRATION,
66 .channel2 = IIO_MOD_CO2,
67 .modified = 1,
68 .info_mask_separate =
69 BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
70 .address = VZ89X_VOC_CO2_IDX,
71 },
72 {
73 .type = IIO_CONCENTRATION,
74 .channel2 = IIO_MOD_VOC,
75 .modified = 1,
76 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
77 .address = VZ89X_VOC_SHORT_IDX,
78 .extend_name = "short",
79 },
80 {
81 .type = IIO_CONCENTRATION,
82 .channel2 = IIO_MOD_VOC,
83 .modified = 1,
84 .info_mask_separate =
85 BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW),
86 .address = VZ89X_VOC_TVOC_IDX,
87 },
88 {
89 .type = IIO_RESISTANCE,
90 .info_mask_separate =
91 BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
92 .address = VZ89X_VOC_RESISTANCE_IDX,
93 },
94};
95
96static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689");
97static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223");
98
99static struct attribute *vz89x_attributes[] = {
100 &iio_const_attr_in_concentration_co2_scale.dev_attr.attr,
101 &iio_const_attr_in_concentration_voc_scale.dev_attr.attr,
102 NULL,
103};
104
105static const struct attribute_group vz89x_attrs_group = {
106 .attrs = vz89x_attributes,
107};
108
Matt Ranostaybf23cee2015-09-22 21:25:09 -0700109/*
110 * Chipset sometime updates in the middle of a reading causing it to reset the
111 * data pointer, and causing invalid reading of previous data.
112 * We can check for this by reading MSB of the resistance reading that is
113 * always zero, and by also confirming the VOC_short isn't zero.
114 */
115
Matt Ranostay83768822016-08-24 23:44:47 -0700116static bool vz89x_measurement_is_valid(struct vz89x_data *data)
Matt Ranostaybf23cee2015-09-22 21:25:09 -0700117{
118 if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0)
119 return 1;
120
Matt Ranostay83768822016-08-24 23:44:47 -0700121 return !!(data->buffer[data->chip->read_size - 1] > 0);
Matt Ranostaybf23cee2015-09-22 21:25:09 -0700122}
123
Matt Ranostay93e87d72015-12-01 21:47:20 -0800124static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd)
125{
Matt Ranostay83768822016-08-24 23:44:47 -0700126 const struct vz89x_chip_data *chip = data->chip;
Matt Ranostay93e87d72015-12-01 21:47:20 -0800127 struct i2c_client *client = data->client;
128 struct i2c_msg msg[2];
129 int ret;
130 u8 buf[3] = { cmd, 0, 0};
131
132 msg[0].addr = client->addr;
133 msg[0].flags = client->flags;
Matt Ranostay83768822016-08-24 23:44:47 -0700134 msg[0].len = chip->write_size;
Matt Ranostay93e87d72015-12-01 21:47:20 -0800135 msg[0].buf = (char *) &buf;
136
137 msg[1].addr = client->addr;
138 msg[1].flags = client->flags | I2C_M_RD;
Matt Ranostay83768822016-08-24 23:44:47 -0700139 msg[1].len = chip->read_size;
Matt Ranostay93e87d72015-12-01 21:47:20 -0800140 msg[1].buf = (char *) &data->buffer;
141
142 ret = i2c_transfer(client->adapter, msg, 2);
143
144 return (ret == 2) ? 0 : ret;
145}
146
147static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd)
148{
149 struct i2c_client *client = data->client;
150 int ret;
151 int i;
152
153 ret = i2c_smbus_write_word_data(client, cmd, 0);
154 if (ret < 0)
155 return ret;
156
Matt Ranostay83768822016-08-24 23:44:47 -0700157 for (i = 0; i < data->chip->read_size; i++) {
Matt Ranostay93e87d72015-12-01 21:47:20 -0800158 ret = i2c_smbus_read_byte(client);
159 if (ret < 0)
160 return ret;
161 data->buffer[i] = ret;
162 }
163
164 return 0;
165}
166
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700167static int vz89x_get_measurement(struct vz89x_data *data)
168{
Matt Ranostay83768822016-08-24 23:44:47 -0700169 const struct vz89x_chip_data *chip = data->chip;
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700170 int ret;
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700171
172 /* sensor can only be polled once a second max per datasheet */
173 if (!time_after(jiffies, data->last_update + HZ))
174 return 0;
175
Matt Ranostay83768822016-08-24 23:44:47 -0700176 ret = data->xfer(data, chip->cmd);
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700177 if (ret < 0)
178 return ret;
179
Matt Ranostay83768822016-08-24 23:44:47 -0700180 ret = chip->valid(data);
Matt Ranostaybf23cee2015-09-22 21:25:09 -0700181 if (ret)
182 return -EAGAIN;
183
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700184 data->last_update = jiffies;
185
186 return 0;
187}
188
189static int vz89x_get_resistance_reading(struct vz89x_data *data)
190{
Matt Ranostaybf23cee2015-09-22 21:25:09 -0700191 u8 *buf = &data->buffer[VZ89X_VOC_RESISTANCE_IDX];
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700192
Matt Ranostaybf23cee2015-09-22 21:25:09 -0700193 return buf[0] | (buf[1] << 8);
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700194}
195
196static int vz89x_read_raw(struct iio_dev *indio_dev,
197 struct iio_chan_spec const *chan, int *val,
198 int *val2, long mask)
199{
200 struct vz89x_data *data = iio_priv(indio_dev);
201 int ret = -EINVAL;
202
203 switch (mask) {
204 case IIO_CHAN_INFO_RAW:
205 mutex_lock(&data->lock);
206 ret = vz89x_get_measurement(data);
207 mutex_unlock(&data->lock);
208
209 if (ret)
210 return ret;
211
212 switch (chan->address) {
213 case VZ89X_VOC_CO2_IDX:
214 case VZ89X_VOC_SHORT_IDX:
215 case VZ89X_VOC_TVOC_IDX:
216 *val = data->buffer[chan->address];
217 return IIO_VAL_INT;
218 case VZ89X_VOC_RESISTANCE_IDX:
219 *val = vz89x_get_resistance_reading(data);
220 return IIO_VAL_INT;
221 default:
222 return -EINVAL;
223 }
224 break;
225 case IIO_CHAN_INFO_SCALE:
226 switch (chan->type) {
227 case IIO_RESISTANCE:
228 *val = 10;
229 return IIO_VAL_INT;
230 default:
231 return -EINVAL;
232 }
233 break;
234 case IIO_CHAN_INFO_OFFSET:
235 switch (chan->address) {
236 case VZ89X_VOC_CO2_IDX:
237 *val = 44;
238 *val2 = 250000;
239 return IIO_VAL_INT_PLUS_MICRO;
240 case VZ89X_VOC_TVOC_IDX:
241 *val = -13;
242 return IIO_VAL_INT;
243 default:
244 return -EINVAL;
245 }
246 }
247
248 return ret;
249}
250
251static const struct iio_info vz89x_info = {
252 .attrs = &vz89x_attrs_group,
253 .read_raw = vz89x_read_raw,
254 .driver_module = THIS_MODULE,
255};
256
Matt Ranostay83768822016-08-24 23:44:47 -0700257static const struct vz89x_chip_data vz89x_chips[] = {
258 {
259 .valid = vz89x_measurement_is_valid,
260
261 .cmd = VZ89X_REG_MEASUREMENT,
262 .read_size = VZ89X_REG_MEASUREMENT_RD_SIZE,
263 .write_size = VZ89X_REG_MEASUREMENT_WR_SIZE,
264
265 .channels = vz89x_channels,
266 .num_channels = ARRAY_SIZE(vz89x_channels),
267 },
268};
269
270static const struct of_device_id vz89x_dt_ids[] = {
271 { .compatible = "sgx,vz89x", .data = (void *) VZ89X },
272 { }
273};
274MODULE_DEVICE_TABLE(of, vz89x_dt_ids);
275
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700276static int vz89x_probe(struct i2c_client *client,
277 const struct i2c_device_id *id)
278{
279 struct iio_dev *indio_dev;
280 struct vz89x_data *data;
Matt Ranostay83768822016-08-24 23:44:47 -0700281 const struct of_device_id *of_id;
282 int chip_id;
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700283
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700284 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
285 if (!indio_dev)
286 return -ENOMEM;
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700287 data = iio_priv(indio_dev);
Matt Ranostay93e87d72015-12-01 21:47:20 -0800288
289 if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
290 data->xfer = vz89x_i2c_xfer;
291 else if (i2c_check_functionality(client->adapter,
292 I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
293 data->xfer = vz89x_smbus_xfer;
294 else
Matt Ranostayf8d9d3b2016-02-26 22:13:49 -0800295 return -EOPNOTSUPP;
Matt Ranostay93e87d72015-12-01 21:47:20 -0800296
Matt Ranostay83768822016-08-24 23:44:47 -0700297 of_id = of_match_device(vz89x_dt_ids, &client->dev);
298 if (!of_id)
299 chip_id = id->driver_data;
300 else
301 chip_id = (unsigned long)of_id->data;
302
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700303 i2c_set_clientdata(client, indio_dev);
304 data->client = client;
Matt Ranostay83768822016-08-24 23:44:47 -0700305 data->chip = &vz89x_chips[chip_id];
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700306 data->last_update = jiffies - HZ;
307 mutex_init(&data->lock);
308
309 indio_dev->dev.parent = &client->dev;
310 indio_dev->info = &vz89x_info,
311 indio_dev->name = dev_name(&client->dev);
312 indio_dev->modes = INDIO_DIRECT_MODE;
313
Matt Ranostay83768822016-08-24 23:44:47 -0700314 indio_dev->channels = data->chip->channels;
315 indio_dev->num_channels = data->chip->num_channels;
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700316
317 return devm_iio_device_register(&client->dev, indio_dev);
318}
319
320static const struct i2c_device_id vz89x_id[] = {
Matt Ranostay83768822016-08-24 23:44:47 -0700321 { "vz89x", VZ89X },
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700322 { }
323};
324MODULE_DEVICE_TABLE(i2c, vz89x_id);
325
Matt Ranostaycd8d97772015-09-13 20:26:14 -0700326static struct i2c_driver vz89x_driver = {
327 .driver = {
328 .name = "vz89x",
329 .of_match_table = of_match_ptr(vz89x_dt_ids),
330 },
331 .probe = vz89x_probe,
332 .id_table = vz89x_id,
333};
334module_i2c_driver(vz89x_driver);
335
336MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
337MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors");
338MODULE_LICENSE("GPL v2");