blob: b45420039ca2bb6848d18c51939fad82c8962358 [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>
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
34struct 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
42static 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
75static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689");
76static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223");
77
78static 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
84static const struct attribute_group vz89x_attrs_group = {
85 .attrs = vz89x_attributes,
86};
87
88static int vz89x_get_measurement(struct vz89x_data *data)
89{
90 int ret;
91 int i;
92
93 /* sensor can only be polled once a second max per datasheet */
94 if (!time_after(jiffies, data->last_update + HZ))
95 return 0;
96
97 ret = i2c_smbus_write_word_data(data->client,
98 VZ89X_REG_MEASUREMENT, 0);
99 if (ret < 0)
100 return ret;
101
102 for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) {
103 ret = i2c_smbus_read_byte(data->client);
104 if (ret < 0)
105 return ret;
106 data->buffer[i] = ret;
107 }
108
109 data->last_update = jiffies;
110
111 return 0;
112}
113
114static int vz89x_get_resistance_reading(struct vz89x_data *data)
115{
116 u8 *buf = &data->buffer[VZ89X_VOC_TVOC_IDX];
117
118 return buf[0] | (buf[1] << 8) | (buf[2] << 16);
119}
120
121static int vz89x_read_raw(struct iio_dev *indio_dev,
122 struct iio_chan_spec const *chan, int *val,
123 int *val2, long mask)
124{
125 struct vz89x_data *data = iio_priv(indio_dev);
126 int ret = -EINVAL;
127
128 switch (mask) {
129 case IIO_CHAN_INFO_RAW:
130 mutex_lock(&data->lock);
131 ret = vz89x_get_measurement(data);
132 mutex_unlock(&data->lock);
133
134 if (ret)
135 return ret;
136
137 switch (chan->address) {
138 case VZ89X_VOC_CO2_IDX:
139 case VZ89X_VOC_SHORT_IDX:
140 case VZ89X_VOC_TVOC_IDX:
141 *val = data->buffer[chan->address];
142 return IIO_VAL_INT;
143 case VZ89X_VOC_RESISTANCE_IDX:
144 *val = vz89x_get_resistance_reading(data);
145 return IIO_VAL_INT;
146 default:
147 return -EINVAL;
148 }
149 break;
150 case IIO_CHAN_INFO_SCALE:
151 switch (chan->type) {
152 case IIO_RESISTANCE:
153 *val = 10;
154 return IIO_VAL_INT;
155 default:
156 return -EINVAL;
157 }
158 break;
159 case IIO_CHAN_INFO_OFFSET:
160 switch (chan->address) {
161 case VZ89X_VOC_CO2_IDX:
162 *val = 44;
163 *val2 = 250000;
164 return IIO_VAL_INT_PLUS_MICRO;
165 case VZ89X_VOC_TVOC_IDX:
166 *val = -13;
167 return IIO_VAL_INT;
168 default:
169 return -EINVAL;
170 }
171 }
172
173 return ret;
174}
175
176static const struct iio_info vz89x_info = {
177 .attrs = &vz89x_attrs_group,
178 .read_raw = vz89x_read_raw,
179 .driver_module = THIS_MODULE,
180};
181
182static int vz89x_probe(struct i2c_client *client,
183 const struct i2c_device_id *id)
184{
185 struct iio_dev *indio_dev;
186 struct vz89x_data *data;
187
188 if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA |
189 I2C_FUNC_SMBUS_BYTE))
190 return -ENODEV;
191
192 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
193 if (!indio_dev)
194 return -ENOMEM;
195
196 data = iio_priv(indio_dev);
197 i2c_set_clientdata(client, indio_dev);
198 data->client = client;
199 data->last_update = jiffies - HZ;
200 mutex_init(&data->lock);
201
202 indio_dev->dev.parent = &client->dev;
203 indio_dev->info = &vz89x_info,
204 indio_dev->name = dev_name(&client->dev);
205 indio_dev->modes = INDIO_DIRECT_MODE;
206
207 indio_dev->channels = vz89x_channels;
208 indio_dev->num_channels = ARRAY_SIZE(vz89x_channels);
209
210 return devm_iio_device_register(&client->dev, indio_dev);
211}
212
213static const struct i2c_device_id vz89x_id[] = {
214 { "vz89x", 0 },
215 { }
216};
217MODULE_DEVICE_TABLE(i2c, vz89x_id);
218
219static const struct of_device_id vz89x_dt_ids[] = {
220 { .compatible = "sgx,vz89x" },
221 { }
222};
223MODULE_DEVICE_TABLE(of, vz89x_dt_ids);
224
225static struct i2c_driver vz89x_driver = {
226 .driver = {
227 .name = "vz89x",
228 .of_match_table = of_match_ptr(vz89x_dt_ids),
229 },
230 .probe = vz89x_probe,
231 .id_table = vz89x_id,
232};
233module_i2c_driver(vz89x_driver);
234
235MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
236MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors");
237MODULE_LICENSE("GPL v2");