1 // SPDX-License-Identifier: GPL-2.0+
3 * ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors
5 * Copyright (C) 2015, 2018
6 * Author: Matt Ranostay <matt.ranostay@konsulko.com>
9 #include <linux/module.h>
10 #include <linux/mutex.h>
11 #include <linux/init.h>
12 #include <linux/i2c.h>
13 #include <linux/iio/iio.h>
15 #define AMS_IAQCORE_DATA_SIZE 9
17 #define AMS_IAQCORE_VOC_CO2_IDX 0
18 #define AMS_IAQCORE_VOC_RESISTANCE_IDX 1
19 #define AMS_IAQCORE_VOC_TVOC_IDX 2
21 struct ams_iaqcore_reading
{
26 } __attribute__((__packed__
));
28 struct ams_iaqcore_data
{
29 struct i2c_client
*client
;
31 unsigned long last_update
;
33 struct ams_iaqcore_reading buffer
;
36 static const struct iio_chan_spec ams_iaqcore_channels
[] = {
38 .type
= IIO_CONCENTRATION
,
39 .channel2
= IIO_MOD_CO2
,
41 .info_mask_separate
= BIT(IIO_CHAN_INFO_PROCESSED
),
42 .address
= AMS_IAQCORE_VOC_CO2_IDX
,
45 .type
= IIO_RESISTANCE
,
46 .info_mask_separate
= BIT(IIO_CHAN_INFO_PROCESSED
),
47 .address
= AMS_IAQCORE_VOC_RESISTANCE_IDX
,
50 .type
= IIO_CONCENTRATION
,
51 .channel2
= IIO_MOD_VOC
,
53 .info_mask_separate
= BIT(IIO_CHAN_INFO_PROCESSED
),
54 .address
= AMS_IAQCORE_VOC_TVOC_IDX
,
58 static int ams_iaqcore_read_measurement(struct ams_iaqcore_data
*data
)
60 struct i2c_client
*client
= data
->client
;
63 struct i2c_msg msg
= {
65 .flags
= client
->flags
| I2C_M_RD
,
66 .len
= AMS_IAQCORE_DATA_SIZE
,
67 .buf
= (char *) &data
->buffer
,
70 ret
= i2c_transfer(client
->adapter
, &msg
, 1);
72 return (ret
== AMS_IAQCORE_DATA_SIZE
) ? 0 : ret
;
75 static int ams_iaqcore_get_measurement(struct ams_iaqcore_data
*data
)
79 /* sensor can only be polled once a second max per datasheet */
80 if (!time_after(jiffies
, data
->last_update
+ HZ
))
83 ret
= ams_iaqcore_read_measurement(data
);
87 data
->last_update
= jiffies
;
92 static int ams_iaqcore_read_raw(struct iio_dev
*indio_dev
,
93 struct iio_chan_spec
const *chan
, int *val
,
96 struct ams_iaqcore_data
*data
= iio_priv(indio_dev
);
99 if (mask
!= IIO_CHAN_INFO_PROCESSED
)
102 mutex_lock(&data
->lock
);
103 ret
= ams_iaqcore_get_measurement(data
);
108 switch (chan
->address
) {
109 case AMS_IAQCORE_VOC_CO2_IDX
:
111 *val2
= be16_to_cpu(data
->buffer
.co2_ppm
);
112 ret
= IIO_VAL_INT_PLUS_MICRO
;
114 case AMS_IAQCORE_VOC_RESISTANCE_IDX
:
115 *val
= be32_to_cpu(data
->buffer
.resistance
);
118 case AMS_IAQCORE_VOC_TVOC_IDX
:
120 *val2
= be16_to_cpu(data
->buffer
.voc_ppb
);
121 ret
= IIO_VAL_INT_PLUS_NANO
;
128 mutex_unlock(&data
->lock
);
133 static const struct iio_info ams_iaqcore_info
= {
134 .read_raw
= ams_iaqcore_read_raw
,
137 static int ams_iaqcore_probe(struct i2c_client
*client
,
138 const struct i2c_device_id
*id
)
140 struct iio_dev
*indio_dev
;
141 struct ams_iaqcore_data
*data
;
143 indio_dev
= devm_iio_device_alloc(&client
->dev
, sizeof(*data
));
147 data
= iio_priv(indio_dev
);
148 i2c_set_clientdata(client
, indio_dev
);
149 data
->client
= client
;
151 /* so initial reading will complete */
152 data
->last_update
= jiffies
- HZ
;
153 mutex_init(&data
->lock
);
155 indio_dev
->dev
.parent
= &client
->dev
;
156 indio_dev
->info
= &ams_iaqcore_info
;
157 indio_dev
->name
= dev_name(&client
->dev
);
158 indio_dev
->modes
= INDIO_DIRECT_MODE
;
160 indio_dev
->channels
= ams_iaqcore_channels
;
161 indio_dev
->num_channels
= ARRAY_SIZE(ams_iaqcore_channels
);
163 return devm_iio_device_register(&client
->dev
, indio_dev
);
166 static const struct i2c_device_id ams_iaqcore_id
[] = {
167 { "ams-iaq-core", 0 },
170 MODULE_DEVICE_TABLE(i2c
, ams_iaqcore_id
);
172 static const struct of_device_id ams_iaqcore_dt_ids
[] = {
173 { .compatible
= "ams,iaq-core" },
176 MODULE_DEVICE_TABLE(of
, ams_iaqcore_dt_ids
);
178 static struct i2c_driver ams_iaqcore_driver
= {
180 .name
= "ams-iaq-core",
181 .of_match_table
= of_match_ptr(ams_iaqcore_dt_ids
),
183 .probe
= ams_iaqcore_probe
,
184 .id_table
= ams_iaqcore_id
,
186 module_i2c_driver(ams_iaqcore_driver
);
188 MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
189 MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors");
190 MODULE_LICENSE("GPL v2");