1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * hdc2010.c - Support for the TI HDC2010 and HDC2080 |
4 | * temperature + relative humidity sensors |
5 | * |
6 | * Copyright (C) 2020 Norphonic AS |
7 | * Author: Eugene Zaikonnikov <ez@norphonic.com> |
8 | * |
9 | * Datasheet: https://www.ti.com/product/HDC2010/datasheet |
10 | * Datasheet: https://www.ti.com/product/HDC2080/datasheet |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/init.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/bitops.h> |
17 | |
18 | #include <linux/iio/iio.h> |
19 | #include <linux/iio/sysfs.h> |
20 | |
21 | #define HDC2010_REG_TEMP_LOW 0x00 |
22 | #define HDC2010_REG_TEMP_HIGH 0x01 |
23 | #define HDC2010_REG_HUMIDITY_LOW 0x02 |
24 | #define HDC2010_REG_HUMIDITY_HIGH 0x03 |
25 | #define HDC2010_REG_INTERRUPT_DRDY 0x04 |
26 | #define HDC2010_REG_TEMP_MAX 0x05 |
27 | #define HDC2010_REG_HUMIDITY_MAX 0x06 |
28 | #define HDC2010_REG_INTERRUPT_EN 0x07 |
29 | #define HDC2010_REG_TEMP_OFFSET_ADJ 0x08 |
30 | #define HDC2010_REG_HUMIDITY_OFFSET_ADJ 0x09 |
31 | #define HDC2010_REG_TEMP_THR_L 0x0a |
32 | #define HDC2010_REG_TEMP_THR_H 0x0b |
33 | #define HDC2010_REG_RH_THR_L 0x0c |
34 | #define HDC2010_REG_RH_THR_H 0x0d |
35 | #define HDC2010_REG_RESET_DRDY_INT_CONF 0x0e |
36 | #define HDC2010_REG_MEASUREMENT_CONF 0x0f |
37 | |
38 | #define HDC2010_MEAS_CONF GENMASK(2, 1) |
39 | #define HDC2010_MEAS_TRIG BIT(0) |
40 | #define HDC2010_HEATER_EN BIT(3) |
41 | #define HDC2010_AMM GENMASK(6, 4) |
42 | |
43 | struct hdc2010_data { |
44 | struct i2c_client *client; |
45 | struct mutex lock; |
46 | u8 measurement_config; |
47 | u8 interrupt_config; |
48 | u8 drdy_config; |
49 | }; |
50 | |
51 | enum hdc2010_addr_groups { |
52 | HDC2010_GROUP_TEMP = 0, |
53 | HDC2010_GROUP_HUMIDITY, |
54 | }; |
55 | |
56 | struct hdc2010_reg_record { |
57 | unsigned long primary; |
58 | unsigned long peak; |
59 | }; |
60 | |
61 | static const struct hdc2010_reg_record hdc2010_reg_translation[] = { |
62 | [HDC2010_GROUP_TEMP] = { |
63 | .primary = HDC2010_REG_TEMP_LOW, |
64 | .peak = HDC2010_REG_TEMP_MAX, |
65 | }, |
66 | [HDC2010_GROUP_HUMIDITY] = { |
67 | .primary = HDC2010_REG_HUMIDITY_LOW, |
68 | .peak = HDC2010_REG_HUMIDITY_MAX, |
69 | }, |
70 | }; |
71 | |
72 | static IIO_CONST_ATTR(out_current_heater_raw_available, "0 1" ); |
73 | |
74 | static struct attribute *hdc2010_attributes[] = { |
75 | &iio_const_attr_out_current_heater_raw_available.dev_attr.attr, |
76 | NULL |
77 | }; |
78 | |
79 | static const struct attribute_group hdc2010_attribute_group = { |
80 | .attrs = hdc2010_attributes, |
81 | }; |
82 | |
83 | static const struct iio_chan_spec hdc2010_channels[] = { |
84 | { |
85 | .type = IIO_TEMP, |
86 | .address = HDC2010_GROUP_TEMP, |
87 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
88 | BIT(IIO_CHAN_INFO_PEAK) | |
89 | BIT(IIO_CHAN_INFO_OFFSET) | |
90 | BIT(IIO_CHAN_INFO_SCALE), |
91 | }, |
92 | { |
93 | .type = IIO_HUMIDITYRELATIVE, |
94 | .address = HDC2010_GROUP_HUMIDITY, |
95 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
96 | BIT(IIO_CHAN_INFO_PEAK) | |
97 | BIT(IIO_CHAN_INFO_SCALE), |
98 | }, |
99 | { |
100 | .type = IIO_CURRENT, |
101 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
102 | .extend_name = "heater" , |
103 | .output = 1, |
104 | }, |
105 | }; |
106 | |
107 | static int hdc2010_update_drdy_config(struct hdc2010_data *data, |
108 | char mask, char val) |
109 | { |
110 | u8 tmp = (~mask & data->drdy_config) | val; |
111 | int ret; |
112 | |
113 | ret = i2c_smbus_write_byte_data(client: data->client, |
114 | HDC2010_REG_RESET_DRDY_INT_CONF, value: tmp); |
115 | if (ret) |
116 | return ret; |
117 | |
118 | data->drdy_config = tmp; |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static int hdc2010_get_prim_measurement_word(struct hdc2010_data *data, |
124 | struct iio_chan_spec const *chan) |
125 | { |
126 | struct i2c_client *client = data->client; |
127 | s32 ret; |
128 | |
129 | ret = i2c_smbus_read_word_data(client, |
130 | command: hdc2010_reg_translation[chan->address].primary); |
131 | |
132 | if (ret < 0) |
133 | dev_err(&client->dev, "Could not read sensor measurement word\n" ); |
134 | |
135 | return ret; |
136 | } |
137 | |
138 | static int hdc2010_get_peak_measurement_byte(struct hdc2010_data *data, |
139 | struct iio_chan_spec const *chan) |
140 | { |
141 | struct i2c_client *client = data->client; |
142 | s32 ret; |
143 | |
144 | ret = i2c_smbus_read_byte_data(client, |
145 | command: hdc2010_reg_translation[chan->address].peak); |
146 | |
147 | if (ret < 0) |
148 | dev_err(&client->dev, "Could not read sensor measurement byte\n" ); |
149 | |
150 | return ret; |
151 | } |
152 | |
153 | static int hdc2010_get_heater_status(struct hdc2010_data *data) |
154 | { |
155 | return !!(data->drdy_config & HDC2010_HEATER_EN); |
156 | } |
157 | |
158 | static int hdc2010_read_raw(struct iio_dev *indio_dev, |
159 | struct iio_chan_spec const *chan, int *val, |
160 | int *val2, long mask) |
161 | { |
162 | struct hdc2010_data *data = iio_priv(indio_dev); |
163 | |
164 | switch (mask) { |
165 | case IIO_CHAN_INFO_RAW: { |
166 | int ret; |
167 | |
168 | if (chan->type == IIO_CURRENT) { |
169 | *val = hdc2010_get_heater_status(data); |
170 | return IIO_VAL_INT; |
171 | } |
172 | ret = iio_device_claim_direct_mode(indio_dev); |
173 | if (ret) |
174 | return ret; |
175 | mutex_lock(&data->lock); |
176 | ret = hdc2010_get_prim_measurement_word(data, chan); |
177 | mutex_unlock(lock: &data->lock); |
178 | iio_device_release_direct_mode(indio_dev); |
179 | if (ret < 0) |
180 | return ret; |
181 | *val = ret; |
182 | return IIO_VAL_INT; |
183 | } |
184 | case IIO_CHAN_INFO_PEAK: { |
185 | int ret; |
186 | |
187 | ret = iio_device_claim_direct_mode(indio_dev); |
188 | if (ret) |
189 | return ret; |
190 | mutex_lock(&data->lock); |
191 | ret = hdc2010_get_peak_measurement_byte(data, chan); |
192 | mutex_unlock(lock: &data->lock); |
193 | iio_device_release_direct_mode(indio_dev); |
194 | if (ret < 0) |
195 | return ret; |
196 | /* Scaling up the value so we can use same offset as RAW */ |
197 | *val = ret * 256; |
198 | return IIO_VAL_INT; |
199 | } |
200 | case IIO_CHAN_INFO_SCALE: |
201 | *val2 = 65536; |
202 | if (chan->type == IIO_TEMP) |
203 | *val = 165000; |
204 | else |
205 | *val = 100000; |
206 | return IIO_VAL_FRACTIONAL; |
207 | case IIO_CHAN_INFO_OFFSET: |
208 | *val = -15887; |
209 | *val2 = 515151; |
210 | return IIO_VAL_INT_PLUS_MICRO; |
211 | default: |
212 | return -EINVAL; |
213 | } |
214 | } |
215 | |
216 | static int hdc2010_write_raw(struct iio_dev *indio_dev, |
217 | struct iio_chan_spec const *chan, |
218 | int val, int val2, long mask) |
219 | { |
220 | struct hdc2010_data *data = iio_priv(indio_dev); |
221 | int new, ret; |
222 | |
223 | switch (mask) { |
224 | case IIO_CHAN_INFO_RAW: |
225 | if (chan->type != IIO_CURRENT || val2 != 0) |
226 | return -EINVAL; |
227 | |
228 | switch (val) { |
229 | case 1: |
230 | new = HDC2010_HEATER_EN; |
231 | break; |
232 | case 0: |
233 | new = 0; |
234 | break; |
235 | default: |
236 | return -EINVAL; |
237 | } |
238 | |
239 | mutex_lock(&data->lock); |
240 | ret = hdc2010_update_drdy_config(data, HDC2010_HEATER_EN, val: new); |
241 | mutex_unlock(lock: &data->lock); |
242 | return ret; |
243 | default: |
244 | return -EINVAL; |
245 | } |
246 | } |
247 | |
248 | static const struct iio_info hdc2010_info = { |
249 | .read_raw = hdc2010_read_raw, |
250 | .write_raw = hdc2010_write_raw, |
251 | .attrs = &hdc2010_attribute_group, |
252 | }; |
253 | |
254 | static int hdc2010_probe(struct i2c_client *client) |
255 | { |
256 | struct iio_dev *indio_dev; |
257 | struct hdc2010_data *data; |
258 | u8 tmp; |
259 | int ret; |
260 | |
261 | if (!i2c_check_functionality(adap: client->adapter, |
262 | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) |
263 | return -EOPNOTSUPP; |
264 | |
265 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
266 | if (!indio_dev) |
267 | return -ENOMEM; |
268 | |
269 | data = iio_priv(indio_dev); |
270 | i2c_set_clientdata(client, data: indio_dev); |
271 | data->client = client; |
272 | mutex_init(&data->lock); |
273 | |
274 | /* |
275 | * As DEVICE ID register does not differentiate between |
276 | * HDC2010 and HDC2080, we have the name hardcoded |
277 | */ |
278 | indio_dev->name = "hdc2010" ; |
279 | indio_dev->modes = INDIO_DIRECT_MODE; |
280 | indio_dev->info = &hdc2010_info; |
281 | |
282 | indio_dev->channels = hdc2010_channels; |
283 | indio_dev->num_channels = ARRAY_SIZE(hdc2010_channels); |
284 | |
285 | /* Enable Automatic Measurement Mode at 5Hz */ |
286 | ret = hdc2010_update_drdy_config(data, HDC2010_AMM, HDC2010_AMM); |
287 | if (ret) |
288 | return ret; |
289 | |
290 | /* |
291 | * We enable both temp and humidity measurement. |
292 | * However the measurement won't start even in AMM until triggered. |
293 | */ |
294 | tmp = (data->measurement_config & ~HDC2010_MEAS_CONF) | |
295 | HDC2010_MEAS_TRIG; |
296 | |
297 | ret = i2c_smbus_write_byte_data(client, HDC2010_REG_MEASUREMENT_CONF, value: tmp); |
298 | if (ret) { |
299 | dev_warn(&client->dev, "Unable to set up measurement\n" ); |
300 | if (hdc2010_update_drdy_config(data, HDC2010_AMM, val: 0)) |
301 | dev_warn(&client->dev, "Unable to restore default AMM\n" ); |
302 | return ret; |
303 | } |
304 | |
305 | data->measurement_config = tmp; |
306 | |
307 | return iio_device_register(indio_dev); |
308 | } |
309 | |
310 | static void hdc2010_remove(struct i2c_client *client) |
311 | { |
312 | struct iio_dev *indio_dev = i2c_get_clientdata(client); |
313 | struct hdc2010_data *data = iio_priv(indio_dev); |
314 | |
315 | iio_device_unregister(indio_dev); |
316 | |
317 | /* Disable Automatic Measurement Mode */ |
318 | if (hdc2010_update_drdy_config(data, HDC2010_AMM, val: 0)) |
319 | dev_warn(&client->dev, "Unable to restore default AMM\n" ); |
320 | } |
321 | |
322 | static const struct i2c_device_id hdc2010_id[] = { |
323 | { "hdc2010" }, |
324 | { "hdc2080" }, |
325 | { } |
326 | }; |
327 | MODULE_DEVICE_TABLE(i2c, hdc2010_id); |
328 | |
329 | static const struct of_device_id hdc2010_dt_ids[] = { |
330 | { .compatible = "ti,hdc2010" }, |
331 | { .compatible = "ti,hdc2080" }, |
332 | { } |
333 | }; |
334 | MODULE_DEVICE_TABLE(of, hdc2010_dt_ids); |
335 | |
336 | static struct i2c_driver hdc2010_driver = { |
337 | .driver = { |
338 | .name = "hdc2010" , |
339 | .of_match_table = hdc2010_dt_ids, |
340 | }, |
341 | .probe = hdc2010_probe, |
342 | .remove = hdc2010_remove, |
343 | .id_table = hdc2010_id, |
344 | }; |
345 | module_i2c_driver(hdc2010_driver); |
346 | |
347 | MODULE_AUTHOR("Eugene Zaikonnikov <ez@norphonic.com>" ); |
348 | MODULE_DESCRIPTION("TI HDC2010 humidity and temperature sensor driver" ); |
349 | MODULE_LICENSE("GPL" ); |
350 | |