1 /*
2  * ams-iaq-core.c - Support for AMS iAQ-Core 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 #include <linux/iio/iio.h>
23 
24 #define AMS_IAQCORE_DATA_SIZE		9
25 
26 #define AMS_IAQCORE_VOC_CO2_IDX		0
27 #define AMS_IAQCORE_VOC_RESISTANCE_IDX	1
28 #define AMS_IAQCORE_VOC_TVOC_IDX	2
29 
30 struct ams_iaqcore_reading {
31 	__be16 co2_ppm;
32 	u8 status;
33 	__be32 resistance;
34 	__be16 voc_ppb;
35 } __attribute__((__packed__));
36 
37 struct ams_iaqcore_data {
38 	struct i2c_client *client;
39 	struct mutex lock;
40 	unsigned long last_update;
41 
42 	struct ams_iaqcore_reading buffer;
43 };
44 
45 static const struct iio_chan_spec ams_iaqcore_channels[] = {
46 	{
47 		.type = IIO_CONCENTRATION,
48 		.channel2 = IIO_MOD_CO2,
49 		.modified = 1,
50 		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
51 		.address = AMS_IAQCORE_VOC_CO2_IDX,
52 	},
53 	{
54 		.type = IIO_RESISTANCE,
55 		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
56 		.address = AMS_IAQCORE_VOC_RESISTANCE_IDX,
57 	},
58 	{
59 		.type = IIO_CONCENTRATION,
60 		.channel2 = IIO_MOD_VOC,
61 		.modified = 1,
62 		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
63 		.address = AMS_IAQCORE_VOC_TVOC_IDX,
64 	},
65 };
66 
67 static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data)
68 {
69 	struct i2c_client *client = data->client;
70 	int ret;
71 
72 	struct i2c_msg msg = {
73 		.addr = client->addr,
74 		.flags = client->flags | I2C_M_RD,
75 		.len = AMS_IAQCORE_DATA_SIZE,
76 		.buf = (char *) &data->buffer,
77 	};
78 
79 	ret = i2c_transfer(client->adapter, &msg, 1);
80 
81 	return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret;
82 }
83 
84 static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data)
85 {
86 	int ret;
87 
88 	/* sensor can only be polled once a second max per datasheet */
89 	if (!time_after(jiffies, data->last_update + HZ))
90 		return 0;
91 
92 	ret = ams_iaqcore_read_measurement(data);
93 	if (ret < 0)
94 		return ret;
95 
96 	data->last_update = jiffies;
97 
98 	return 0;
99 }
100 
101 static int ams_iaqcore_read_raw(struct iio_dev *indio_dev,
102 				struct iio_chan_spec const *chan, int *val,
103 				int *val2, long mask)
104 {
105 	struct ams_iaqcore_data *data = iio_priv(indio_dev);
106 	int ret;
107 
108 	if (mask != IIO_CHAN_INFO_PROCESSED)
109 		return -EINVAL;
110 
111 	mutex_lock(&data->lock);
112 	ret = ams_iaqcore_get_measurement(data);
113 
114 	if (ret)
115 		goto err_out;
116 
117 	switch (chan->address) {
118 	case AMS_IAQCORE_VOC_CO2_IDX:
119 		*val = 0;
120 		*val2 = be16_to_cpu(data->buffer.co2_ppm);
121 		ret = IIO_VAL_INT_PLUS_MICRO;
122 		break;
123 	case AMS_IAQCORE_VOC_RESISTANCE_IDX:
124 		*val = be32_to_cpu(data->buffer.resistance);
125 		ret = IIO_VAL_INT;
126 		break;
127 	case AMS_IAQCORE_VOC_TVOC_IDX:
128 		*val = 0;
129 		*val2 = be16_to_cpu(data->buffer.voc_ppb);
130 		ret = IIO_VAL_INT_PLUS_NANO;
131 		break;
132 	default:
133 		ret = -EINVAL;
134 	}
135 
136 err_out:
137 	mutex_unlock(&data->lock);
138 
139 	return ret;
140 }
141 
142 static const struct iio_info ams_iaqcore_info = {
143 	.read_raw	= ams_iaqcore_read_raw,
144 };
145 
146 static int ams_iaqcore_probe(struct i2c_client *client,
147 			     const struct i2c_device_id *id)
148 {
149 	struct iio_dev *indio_dev;
150 	struct ams_iaqcore_data *data;
151 
152 	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
153 	if (!indio_dev)
154 		return -ENOMEM;
155 
156 	data = iio_priv(indio_dev);
157 	i2c_set_clientdata(client, indio_dev);
158 	data->client = client;
159 
160 	/* so initial reading will complete */
161 	data->last_update = jiffies - HZ;
162 	mutex_init(&data->lock);
163 
164 	indio_dev->dev.parent = &client->dev;
165 	indio_dev->info = &ams_iaqcore_info;
166 	indio_dev->name = dev_name(&client->dev);
167 	indio_dev->modes = INDIO_DIRECT_MODE;
168 
169 	indio_dev->channels = ams_iaqcore_channels;
170 	indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels);
171 
172 	return devm_iio_device_register(&client->dev, indio_dev);
173 }
174 
175 static const struct i2c_device_id ams_iaqcore_id[] = {
176 	{ "ams-iaq-core", 0 },
177 	{ }
178 };
179 MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id);
180 
181 static const struct of_device_id ams_iaqcore_dt_ids[] = {
182 	{ .compatible = "ams,iaq-core" },
183 	{ }
184 };
185 MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids);
186 
187 static struct i2c_driver ams_iaqcore_driver = {
188 	.driver = {
189 		.name	= "ams-iaq-core",
190 		.of_match_table = of_match_ptr(ams_iaqcore_dt_ids),
191 	},
192 	.probe = ams_iaqcore_probe,
193 	.id_table = ams_iaqcore_id,
194 };
195 module_i2c_driver(ams_iaqcore_driver);
196 
197 MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
198 MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors");
199 MODULE_LICENSE("GPL v2");
200