diff options
author | Matt Ranostay <mranostay@gmail.com> | 2015-12-06 01:58:22 -0500 |
---|---|---|
committer | Jonathan Cameron <jic23@kernel.org> | 2015-12-12 10:07:38 -0500 |
commit | 466df4d0c1a5edee243698bdcad1ec4f3a1799b1 (patch) | |
tree | 9897fcab66262e652748165414dbc754a0173300 | |
parent | 4d33615df58bf308626489cbfb8acbc8bbd45658 (diff) |
iio: chemical: add AMS iAQ-core support
Add support for AMS iAQ-core continuous and pulsed VOC sensors.
Signed-off-by: Matt Ranostay <mranostay@gmail.com>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
-rw-r--r-- | Documentation/devicetree/bindings/i2c/trivial-devices.txt | 1 | ||||
-rw-r--r-- | drivers/iio/chemical/Kconfig | 8 | ||||
-rw-r--r-- | drivers/iio/chemical/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/chemical/ams-iaq-core.c | 200 |
4 files changed, 210 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index c50cf13c852e..f6fec952d683 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt | |||
@@ -20,6 +20,7 @@ adi,adt7476 +/-1C TDM Extended Temp Range I.C | |||
20 | adi,adt7490 +/-1C TDM Extended Temp Range I.C | 20 | adi,adt7490 +/-1C TDM Extended Temp Range I.C |
21 | adi,adxl345 Three-Axis Digital Accelerometer | 21 | adi,adxl345 Three-Axis Digital Accelerometer |
22 | adi,adxl346 Three-Axis Digital Accelerometer (backward-compatibility value "adi,adxl345" must be listed too) | 22 | adi,adxl346 Three-Axis Digital Accelerometer (backward-compatibility value "adi,adxl345" must be listed too) |
23 | ams,iaq-core AMS iAQ-Core VOC Sensor | ||
23 | at,24c08 i2c serial eeprom (24cxx) | 24 | at,24c08 i2c serial eeprom (24cxx) |
24 | atmel,24c00 i2c serial eeprom (24cxx) | 25 | atmel,24c00 i2c serial eeprom (24cxx) |
25 | atmel,24c01 i2c serial eeprom (24cxx) | 26 | atmel,24c01 i2c serial eeprom (24cxx) |
diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index 3061b7299f0f..f16de61be46d 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig | |||
@@ -4,6 +4,14 @@ | |||
4 | 4 | ||
5 | menu "Chemical Sensors" | 5 | menu "Chemical Sensors" |
6 | 6 | ||
7 | config IAQCORE | ||
8 | tristate "AMS iAQ-Core VOC sensors" | ||
9 | depends on I2C | ||
10 | help | ||
11 | Say Y here to build I2C interface support for the AMS | ||
12 | iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds) | ||
13 | sensors | ||
14 | |||
7 | config VZ89X | 15 | config VZ89X |
8 | tristate "SGX Sensortech MiCS VZ89X VOC sensor" | 16 | tristate "SGX Sensortech MiCS VZ89X VOC sensor" |
9 | depends on I2C | 17 | depends on I2C |
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index 7292f2ded587..167861fadfab 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile | |||
@@ -3,4 +3,5 @@ | |||
3 | # | 3 | # |
4 | 4 | ||
5 | # When adding new entries keep the list in alphabetical order | 5 | # When adding new entries keep the list in alphabetical order |
6 | obj-$(CONFIG_IAQCORE) += ams-iaq-core.o | ||
6 | obj-$(CONFIG_VZ89X) += vz89x.o | 7 | obj-$(CONFIG_VZ89X) += vz89x.o |
diff --git a/drivers/iio/chemical/ams-iaq-core.c b/drivers/iio/chemical/ams-iaq-core.c new file mode 100644 index 000000000000..41a8e6f2e31d --- /dev/null +++ b/drivers/iio/chemical/ams-iaq-core.c | |||
@@ -0,0 +1,200 @@ | |||
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 | .driver_module = THIS_MODULE, | ||
145 | }; | ||
146 | |||
147 | static int ams_iaqcore_probe(struct i2c_client *client, | ||
148 | const struct i2c_device_id *id) | ||
149 | { | ||
150 | struct iio_dev *indio_dev; | ||
151 | struct ams_iaqcore_data *data; | ||
152 | |||
153 | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); | ||
154 | if (!indio_dev) | ||
155 | return -ENOMEM; | ||
156 | |||
157 | data = iio_priv(indio_dev); | ||
158 | i2c_set_clientdata(client, indio_dev); | ||
159 | data->client = client; | ||
160 | |||
161 | /* so initial reading will complete */ | ||
162 | data->last_update = jiffies - HZ; | ||
163 | mutex_init(&data->lock); | ||
164 | |||
165 | indio_dev->dev.parent = &client->dev; | ||
166 | indio_dev->info = &ams_iaqcore_info, | ||
167 | indio_dev->name = dev_name(&client->dev); | ||
168 | indio_dev->modes = INDIO_DIRECT_MODE; | ||
169 | |||
170 | indio_dev->channels = ams_iaqcore_channels; | ||
171 | indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels); | ||
172 | |||
173 | return devm_iio_device_register(&client->dev, indio_dev); | ||
174 | } | ||
175 | |||
176 | static const struct i2c_device_id ams_iaqcore_id[] = { | ||
177 | { "ams-iaq-core", 0 }, | ||
178 | { } | ||
179 | }; | ||
180 | MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id); | ||
181 | |||
182 | static const struct of_device_id ams_iaqcore_dt_ids[] = { | ||
183 | { .compatible = "ams,iaq-core" }, | ||
184 | { } | ||
185 | }; | ||
186 | MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids); | ||
187 | |||
188 | static struct i2c_driver ams_iaqcore_driver = { | ||
189 | .driver = { | ||
190 | .name = "ams-iaq-core", | ||
191 | .of_match_table = of_match_ptr(ams_iaqcore_dt_ids), | ||
192 | }, | ||
193 | .probe = ams_iaqcore_probe, | ||
194 | .id_table = ams_iaqcore_id, | ||
195 | }; | ||
196 | module_i2c_driver(ams_iaqcore_driver); | ||
197 | |||
198 | MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>"); | ||
199 | MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors"); | ||
200 | MODULE_LICENSE("GPL v2"); | ||