1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * atlas-ezo-sensor.c - Support for Atlas Scientific EZO sensors |
4 | * |
5 | * Copyright (C) 2020 Konsulko Group |
6 | * Author: Matt Ranostay <matt.ranostay@konsulko.com> |
7 | */ |
8 | |
9 | #include <linux/init.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/mod_devicetable.h> |
12 | #include <linux/module.h> |
13 | #include <linux/mutex.h> |
14 | #include <linux/property.h> |
15 | #include <linux/err.h> |
16 | #include <linux/i2c.h> |
17 | |
18 | #include <linux/iio/iio.h> |
19 | |
20 | #define ATLAS_EZO_DRV_NAME "atlas-ezo-sensor" |
21 | #define ATLAS_INT_TIME_IN_MS 950 |
22 | #define ATLAS_INT_HUM_TIME_IN_MS 350 |
23 | |
24 | enum { |
25 | ATLAS_CO2_EZO, |
26 | ATLAS_O2_EZO, |
27 | ATLAS_HUM_EZO, |
28 | }; |
29 | |
30 | struct atlas_ezo_device { |
31 | const struct iio_chan_spec *channels; |
32 | int num_channels; |
33 | int delay; |
34 | }; |
35 | |
36 | struct atlas_ezo_data { |
37 | struct i2c_client *client; |
38 | const struct atlas_ezo_device *chip; |
39 | |
40 | /* lock to avoid multiple concurrent read calls */ |
41 | struct mutex lock; |
42 | |
43 | u8 buffer[8]; |
44 | }; |
45 | |
46 | #define ATLAS_CONCENTRATION_CHANNEL(_modifier) \ |
47 | { \ |
48 | .type = IIO_CONCENTRATION, \ |
49 | .modified = 1,\ |
50 | .channel2 = _modifier, \ |
51 | .info_mask_separate = \ |
52 | BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ |
53 | .scan_index = 0, \ |
54 | .scan_type = { \ |
55 | .sign = 'u', \ |
56 | .realbits = 32, \ |
57 | .storagebits = 32, \ |
58 | .endianness = IIO_CPU, \ |
59 | }, \ |
60 | } |
61 | |
62 | static const struct iio_chan_spec atlas_co2_ezo_channels[] = { |
63 | ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_CO2), |
64 | }; |
65 | |
66 | static const struct iio_chan_spec atlas_o2_ezo_channels[] = { |
67 | ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_O2), |
68 | }; |
69 | |
70 | static const struct iio_chan_spec atlas_hum_ezo_channels[] = { |
71 | { |
72 | .type = IIO_HUMIDITYRELATIVE, |
73 | .info_mask_separate = |
74 | BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), |
75 | .scan_index = 0, |
76 | .scan_type = { |
77 | .sign = 'u', |
78 | .realbits = 32, |
79 | .storagebits = 32, |
80 | .endianness = IIO_CPU, |
81 | }, |
82 | }, |
83 | }; |
84 | |
85 | static struct atlas_ezo_device atlas_ezo_devices[] = { |
86 | [ATLAS_CO2_EZO] = { |
87 | .channels = atlas_co2_ezo_channels, |
88 | .num_channels = 1, |
89 | .delay = ATLAS_INT_TIME_IN_MS, |
90 | }, |
91 | [ATLAS_O2_EZO] = { |
92 | .channels = atlas_o2_ezo_channels, |
93 | .num_channels = 1, |
94 | .delay = ATLAS_INT_TIME_IN_MS, |
95 | }, |
96 | [ATLAS_HUM_EZO] = { |
97 | .channels = atlas_hum_ezo_channels, |
98 | .num_channels = 1, |
99 | .delay = ATLAS_INT_HUM_TIME_IN_MS, |
100 | }, |
101 | }; |
102 | |
103 | static void atlas_ezo_sanitize(char *buf) |
104 | { |
105 | char *ptr = strchr(buf, '.'); |
106 | |
107 | if (!ptr) |
108 | return; |
109 | |
110 | memmove(ptr, ptr + 1, strlen(ptr)); |
111 | } |
112 | |
113 | static int atlas_ezo_read_raw(struct iio_dev *indio_dev, |
114 | struct iio_chan_spec const *chan, |
115 | int *val, int *val2, long mask) |
116 | { |
117 | struct atlas_ezo_data *data = iio_priv(indio_dev); |
118 | struct i2c_client *client = data->client; |
119 | |
120 | if (chan->type != IIO_CONCENTRATION) |
121 | return -EINVAL; |
122 | |
123 | switch (mask) { |
124 | case IIO_CHAN_INFO_RAW: { |
125 | int ret; |
126 | long tmp; |
127 | |
128 | mutex_lock(&data->lock); |
129 | |
130 | tmp = i2c_smbus_write_byte(client, value: 'R'); |
131 | |
132 | if (tmp < 0) { |
133 | mutex_unlock(lock: &data->lock); |
134 | return tmp; |
135 | } |
136 | |
137 | msleep(msecs: data->chip->delay); |
138 | |
139 | tmp = i2c_master_recv(client, buf: data->buffer, count: sizeof(data->buffer)); |
140 | |
141 | if (tmp < 0 || data->buffer[0] != 1) { |
142 | mutex_unlock(lock: &data->lock); |
143 | return -EBUSY; |
144 | } |
145 | |
146 | /* removing floating point for fixed number representation */ |
147 | atlas_ezo_sanitize(buf: data->buffer + 2); |
148 | |
149 | ret = kstrtol(s: data->buffer + 1, base: 10, res: &tmp); |
150 | |
151 | *val = tmp; |
152 | |
153 | mutex_unlock(lock: &data->lock); |
154 | |
155 | return ret ? ret : IIO_VAL_INT; |
156 | } |
157 | case IIO_CHAN_INFO_SCALE: |
158 | switch (chan->type) { |
159 | case IIO_HUMIDITYRELATIVE: |
160 | *val = 10; |
161 | return IIO_VAL_INT; |
162 | case IIO_CONCENTRATION: |
163 | break; |
164 | default: |
165 | return -EINVAL; |
166 | } |
167 | |
168 | /* IIO_CONCENTRATION modifiers */ |
169 | switch (chan->channel2) { |
170 | case IIO_MOD_CO2: |
171 | *val = 0; |
172 | *val2 = 100; /* 0.0001 */ |
173 | return IIO_VAL_INT_PLUS_MICRO; |
174 | case IIO_MOD_O2: |
175 | *val = 100; |
176 | return IIO_VAL_INT; |
177 | } |
178 | return -EINVAL; |
179 | } |
180 | |
181 | return 0; |
182 | } |
183 | |
184 | static const struct iio_info atlas_info = { |
185 | .read_raw = atlas_ezo_read_raw, |
186 | }; |
187 | |
188 | static const struct i2c_device_id atlas_ezo_id[] = { |
189 | { "atlas-co2-ezo" , (kernel_ulong_t)&atlas_ezo_devices[ATLAS_CO2_EZO] }, |
190 | { "atlas-o2-ezo" , (kernel_ulong_t)&atlas_ezo_devices[ATLAS_O2_EZO] }, |
191 | { "atlas-hum-ezo" , (kernel_ulong_t)&atlas_ezo_devices[ATLAS_HUM_EZO] }, |
192 | {} |
193 | }; |
194 | MODULE_DEVICE_TABLE(i2c, atlas_ezo_id); |
195 | |
196 | static const struct of_device_id atlas_ezo_dt_ids[] = { |
197 | { .compatible = "atlas,co2-ezo" , .data = &atlas_ezo_devices[ATLAS_CO2_EZO], }, |
198 | { .compatible = "atlas,o2-ezo" , .data = &atlas_ezo_devices[ATLAS_O2_EZO], }, |
199 | { .compatible = "atlas,hum-ezo" , .data = &atlas_ezo_devices[ATLAS_HUM_EZO], }, |
200 | {} |
201 | }; |
202 | MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids); |
203 | |
204 | static int atlas_ezo_probe(struct i2c_client *client) |
205 | { |
206 | const struct atlas_ezo_device *chip; |
207 | struct atlas_ezo_data *data; |
208 | struct iio_dev *indio_dev; |
209 | |
210 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
211 | if (!indio_dev) |
212 | return -ENOMEM; |
213 | |
214 | chip = i2c_get_match_data(client); |
215 | if (!chip) |
216 | return -EINVAL; |
217 | |
218 | indio_dev->info = &atlas_info; |
219 | indio_dev->name = ATLAS_EZO_DRV_NAME; |
220 | indio_dev->channels = chip->channels; |
221 | indio_dev->num_channels = chip->num_channels; |
222 | indio_dev->modes = INDIO_DIRECT_MODE; |
223 | |
224 | data = iio_priv(indio_dev); |
225 | data->client = client; |
226 | data->chip = chip; |
227 | mutex_init(&data->lock); |
228 | |
229 | return devm_iio_device_register(&client->dev, indio_dev); |
230 | }; |
231 | |
232 | static struct i2c_driver atlas_ezo_driver = { |
233 | .driver = { |
234 | .name = ATLAS_EZO_DRV_NAME, |
235 | .of_match_table = atlas_ezo_dt_ids, |
236 | }, |
237 | .probe = atlas_ezo_probe, |
238 | .id_table = atlas_ezo_id, |
239 | }; |
240 | module_i2c_driver(atlas_ezo_driver); |
241 | |
242 | MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>" ); |
243 | MODULE_DESCRIPTION("Atlas Scientific EZO sensors" ); |
244 | MODULE_LICENSE("GPL" ); |
245 | |