xref: /openbmc/linux/drivers/iio/chemical/vz89x.c (revision a8da474e)
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