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