xref: /freebsd/sys/dev/iicbus/sensor/htu21.c (revision 7c569caa0a6fffa7e1cc0a7f61e986dbc7c59074)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021 Andriy Gapon
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions, and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
19  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  */
28 
29 #include <sys/cdefs.h>
30 #include "opt_platform.h"
31 
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 #include <sys/kernel.h>
35 #include <sys/module.h>
36 #include <sys/sysctl.h>
37 #include <sys/systm.h>
38 
39 #include <machine/bus.h>
40 
41 #include <dev/iicbus/iicbus.h>
42 #include <dev/iicbus/iiconf.h>
43 
44 #ifdef FDT
45 #include <dev/ofw/ofw_bus.h>
46 #include <dev/ofw/ofw_bus_subr.h>
47 #endif
48 
49 /*
50  * Driver for HTU21D and compatible temperature and humidity sensors.
51  * Reference documents:
52  * - Measurement Specialties HTU21D datasheet,
53  * - Sensirion SHT21 datasheet,
54  * - Silicon Labs Si7021 datasheet,
55  * - HTU2X Serial Number Reading application note,
56  * - Sensirion Electronic Identification Code (How to read-out the serial number
57  *   of SHT2x) application note.
58  */
59 #define	HTU21_ADDR		0x40
60 
61 #define	HTU21_GET_TEMP		0xe3
62 #define	HTU21_GET_HUM		0xe5
63 #define	HTU21_GET_TEMP_NH	0xf3
64 #define	HTU21_GET_HUM_NH	0xf5
65 #define	HTU21_WRITE_CFG		0xe6
66 #define	HTU21_READ_CFG		0xe7
67 #define	HTU21_RESET		0xfe
68 
69 #define	HTU2x_SERIAL0_0		0xfa
70 #define	HTU2x_SERIAL0_1		0x0f
71 #define	HTU2x_SERIAL1_0		0xfc
72 #define	HTU2x_SERIAL1_1		0xc9
73 
74 struct htu21_softc {
75 	device_t		sc_dev;
76 	uint32_t		sc_addr;
77 	uint8_t			sc_serial[8];
78 	int			sc_errcount;
79 	bool			sc_hold;
80 };
81 
82 #ifdef FDT
83 static struct ofw_compat_data compat_data[] = {
84 	{ "meas,htu21",		true },
85 	{ NULL,			false }
86 };
87 #endif
88 
89 static uint8_t
calc_crc(uint16_t data)90 calc_crc(uint16_t data)
91 {
92 	static const uint16_t polynomial = 0x3100;
93 	int i;
94 
95 	for (i = 0; i < 16; i++) {
96 		int msb_neq = data & 0x8000;
97 
98 		data <<= 1;
99 		if (msb_neq)
100 			data ^= polynomial;
101 	}
102 	return (data >> 8);
103 }
104 
105 static int
check_crc_16(const uint8_t * data,uint8_t expected)106 check_crc_16(const uint8_t *data, uint8_t expected)
107 {
108 	uint8_t crc;
109 
110 	crc = calc_crc(((uint16_t)data[0] << 8) | data[1]);
111 	return (crc == expected);
112 }
113 
114 static int
check_crc_8(const uint8_t data,uint8_t expected)115 check_crc_8(const uint8_t data, uint8_t expected)
116 {
117 	uint8_t crc;
118 
119 	crc = calc_crc(data);
120 	return (crc == expected);
121 }
122 
123 static int
htu21_get_measurement(device_t dev,uint8_t cmd,uint8_t * data,int count)124 htu21_get_measurement(device_t dev, uint8_t cmd, uint8_t *data, int count)
125 {
126 
127 	struct iic_msg msgs[2];
128 	struct htu21_softc *sc;
129 	int error;
130 
131 	sc = device_get_softc(dev);
132 	msgs[0].slave = sc->sc_addr;
133 	msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
134 	msgs[0].len = 1;
135 	msgs[0].buf = &cmd;
136 
137 	msgs[1].slave = sc->sc_addr;
138 	msgs[1].flags = IIC_M_RD;
139 	msgs[1].len = count;
140 	msgs[1].buf = data;
141 
142 	error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
143 	return (error);
144 }
145 
146 static int
htu21_get_measurement_nohold(device_t dev,uint8_t cmd,uint8_t * data,int count)147 htu21_get_measurement_nohold(device_t dev, uint8_t cmd,
148     uint8_t *data, int count)
149 {
150 	struct iic_msg msgs[2];
151 	struct htu21_softc *sc;
152 	int error;
153 	int i;
154 
155 	sc = device_get_softc(dev);
156 
157 	msgs[0].slave = sc->sc_addr;
158 	msgs[0].flags = IIC_M_WR;
159 	msgs[0].len = 1;
160 	msgs[0].buf = &cmd;
161 
162 	msgs[1].slave = sc->sc_addr;
163 	msgs[1].flags = IIC_M_RD;
164 	msgs[1].len = count;
165 	msgs[1].buf = data;
166 
167 	error = iicbus_transfer_excl(dev, &msgs[0], 1, IIC_INTRWAIT);
168 	if (error != 0)
169 		return (error);
170 
171 	for (i = 0; i < hz; i++) {
172 		error = iicbus_transfer_excl(dev, &msgs[1], 1, IIC_INTRWAIT);
173 		if (error == 0)
174 			return (0);
175 		if (error != IIC_ENOACK)
176 			break;
177 		pause("htu21", 1);
178 	}
179 	return (error);
180 }
181 
182 static int
htu21_temp_sysctl(SYSCTL_HANDLER_ARGS)183 htu21_temp_sysctl(SYSCTL_HANDLER_ARGS)
184 {
185 	struct htu21_softc *sc;
186 	device_t dev;
187 	uint8_t raw_data[3];
188 	int error, temp;
189 
190 	dev = arg1;
191 	sc = device_get_softc(dev);
192 
193 	if (req->oldptr != NULL) {
194 		if (sc->sc_hold)
195 			error = htu21_get_measurement(dev, HTU21_GET_TEMP,
196 			    raw_data, nitems(raw_data));
197 		else
198 			error = htu21_get_measurement_nohold(dev,
199 			    HTU21_GET_TEMP_NH, raw_data, nitems(raw_data));
200 
201 		if (error != 0) {
202 			return (EIO);
203 		} else if (!check_crc_16(raw_data, raw_data[2])) {
204 			temp = -1;
205 			sc->sc_errcount++;
206 		} else {
207 			temp = (((uint16_t)raw_data[0]) << 8) |
208 			    (raw_data[1] & 0xfc);
209 			temp = ((temp * 17572) >> 16 ) + 27315 - 4685;
210 		}
211 	}
212 
213 	error = sysctl_handle_int(oidp, &temp, 0, req);
214 	return (error);
215 }
216 
217 static int
htu21_rh_sysctl(SYSCTL_HANDLER_ARGS)218 htu21_rh_sysctl(SYSCTL_HANDLER_ARGS)
219 {
220 	struct htu21_softc *sc;
221 	device_t dev;
222 	uint8_t raw_data[3];
223 	int error, rh;
224 
225 	dev = arg1;
226 	sc = device_get_softc(dev);
227 
228 	if (req->oldptr != NULL) {
229 		if (sc->sc_hold)
230 			error = htu21_get_measurement(dev, HTU21_GET_HUM,
231 			    raw_data, nitems(raw_data));
232 		else
233 			error = htu21_get_measurement_nohold(dev,
234 			    HTU21_GET_HUM_NH, raw_data, nitems(raw_data));
235 
236 		if (error != 0) {
237 			return (EIO);
238 		} else if (!check_crc_16(raw_data, raw_data[2])) {
239 			rh = -1;
240 			sc->sc_errcount++;
241 		} else {
242 			rh = (((uint16_t)raw_data[0]) << 8) |
243 			    (raw_data[1] & 0xfc);
244 			rh = ((rh * 12500) >> 16 ) - 600;
245 		}
246 	}
247 
248 	error = sysctl_handle_int(oidp, &rh, 0, req);
249 	return (error);
250 }
251 
252 static int
htu21_get_cfg(device_t dev,uint8_t * cfg)253 htu21_get_cfg(device_t dev, uint8_t *cfg)
254 {
255 
256 	struct iic_msg msgs[2];
257 	struct htu21_softc *sc;
258 	uint8_t cmd;
259 	int error;
260 
261 	sc = device_get_softc(dev);
262 	cmd = HTU21_READ_CFG;
263 	msgs[0].slave = sc->sc_addr;
264 	msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
265 	msgs[0].len = 1;
266 	msgs[0].buf = &cmd;
267 
268 	msgs[1].slave = sc->sc_addr;
269 	msgs[1].flags = IIC_M_RD;
270 	msgs[1].len = 1;
271 	msgs[1].buf = cfg;
272 
273 	error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
274 	return (error);
275 }
276 
277 static int
htu21_set_cfg(device_t dev,uint8_t cfg)278 htu21_set_cfg(device_t dev, uint8_t cfg)
279 {
280 
281 	struct iic_msg msg;
282 	struct htu21_softc *sc;
283 	uint8_t buf[2];
284 	int error;
285 
286 	sc = device_get_softc(dev);
287 	buf[0] = HTU21_WRITE_CFG;
288 	buf[1] = cfg;
289 	msg.slave = sc->sc_addr;
290 	msg.flags = IIC_M_WR;
291 	msg.len = 2;
292 	msg.buf = buf;
293 
294 	error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT);
295 	return (error);
296 }
297 
298 static int
htu21_heater_sysctl(SYSCTL_HANDLER_ARGS)299 htu21_heater_sysctl(SYSCTL_HANDLER_ARGS)
300 {
301 	device_t dev;
302 	uint8_t cfg;
303 	int error, heater;
304 
305 	dev = arg1;
306 
307 	if (req->oldptr != NULL) {
308 		error = htu21_get_cfg(dev, &cfg);
309 		if (error != 0)
310 			return (EIO);
311 		heater = (cfg & 0x04) != 0;
312 	}
313 	error = sysctl_handle_int(oidp, &heater, 0, req);
314 	if (error != 0 || req->newptr == NULL)
315 		return (error);
316 
317 	cfg &= ~0x04;
318 	cfg |= (heater > 0) << 2;
319 	error = htu21_set_cfg(dev, cfg);
320 	return (error != 0 ? EIO : 0);
321 }
322 
323 static int
htu21_power_sysctl(SYSCTL_HANDLER_ARGS)324 htu21_power_sysctl(SYSCTL_HANDLER_ARGS)
325 {
326 	device_t dev;
327 	uint8_t cfg;
328 	int error, power;
329 
330 	dev = arg1;
331 
332 	if (req->oldptr != NULL) {
333 		error = htu21_get_cfg(dev, &cfg);
334 		if (error != 0)
335 			return (EIO);
336 		power = (cfg & 0x40) == 0;
337 	}
338 	error = sysctl_handle_int(oidp, &power, 0, req);
339 	return (error);
340 }
341 
342 /*
343  * May be incompatible with some chips like SHT21 and Si7021.
344  */
345 static int
htu21_get_serial(device_t dev)346 htu21_get_serial(device_t dev)
347 {
348 
349 	struct iic_msg msgs[2];
350 	struct htu21_softc *sc;
351 	uint8_t data[8];
352 	uint8_t cmd[2];
353 	int error, cksum_err;
354 	int i;
355 
356 	sc = device_get_softc(dev);
357 	cmd[0] = HTU2x_SERIAL0_0;
358 	cmd[1] = HTU2x_SERIAL0_1;
359 	msgs[0].slave = sc->sc_addr;
360 	msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
361 	msgs[0].len = nitems(cmd);
362 	msgs[0].buf = cmd;
363 
364 	msgs[1].slave = sc->sc_addr;
365 	msgs[1].flags = IIC_M_RD;
366 	msgs[1].len = nitems(data);
367 	msgs[1].buf = data;
368 
369 	error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
370 	if (error != 0)
371 		return (EIO);
372 
373 	cksum_err = 0;
374 	for (i = 0; i < nitems(data); i += 2) {
375 		if (!check_crc_8(data[i], data[i + 1]))
376 			cksum_err = EINVAL;
377 		sc->sc_serial[2 + i / 2] = data[i];
378 	}
379 
380 	cmd[0] = HTU2x_SERIAL1_0;
381 	cmd[1] = HTU2x_SERIAL1_1;
382 	msgs[0].slave = sc->sc_addr;
383 	msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
384 	msgs[0].len = nitems(cmd);
385 	msgs[0].buf = cmd;
386 
387 	msgs[1].slave = sc->sc_addr;
388 	msgs[1].flags = IIC_M_RD;
389 	msgs[1].len = 6;
390 	msgs[1].buf = data;
391 
392 	error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
393 	if (error != 0)
394 		return (EIO);
395 
396 	if (!check_crc_16(&data[0], data[2]))
397 		cksum_err = EINVAL;
398 	sc->sc_serial[6] = data[0];
399 	sc->sc_serial[7] = data[1];
400 
401 	if (!check_crc_16(&data[3], data[5]))
402 		cksum_err = EINVAL;
403 	sc->sc_serial[0] = data[3];
404 	sc->sc_serial[1] = data[4];
405 
406 	return (cksum_err);
407 }
408 
409 static void
htu21_start(void * arg)410 htu21_start(void *arg)
411 {
412 	device_t dev;
413 	struct htu21_softc *sc;
414 	struct sysctl_ctx_list *ctx;
415 	struct sysctl_oid *tree_node;
416 	struct sysctl_oid_list *tree;
417 	int error;
418 
419 	sc = arg;
420 	dev = sc->sc_dev;
421 
422 	for (int i = 0; i < 5; i++) {
423 		error = htu21_get_serial(dev);
424 		if (error == 0)
425 			break;
426 	}
427 	if (error != EIO) {
428 		device_printf(dev, "serial number: %8D (checksum %scorrect)\n",
429 		    sc->sc_serial, ":", error == 0 ? "" : "in");
430 	} else {
431 		device_printf(dev, "failed to get serial number, err = %d\n",
432 		    error);
433 	}
434 
435 	ctx = device_get_sysctl_ctx(dev);
436 	tree_node = device_get_sysctl_tree(dev);
437 	tree = SYSCTL_CHILDREN(tree_node);
438 
439 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "temperature",
440 	    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
441 	    htu21_temp_sysctl, "IK2", "Current temperature");
442 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "humidity",
443 	    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
444 	    htu21_rh_sysctl, "I", "Relative humidity in 0.01%% units");
445 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "heater",
446 	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, 0,
447 	    htu21_heater_sysctl, "IU", "Enable built-in heater");
448 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "power",
449 	    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
450 	    htu21_power_sysctl, "IU", "If sensor's power is good");
451 	SYSCTL_ADD_BOOL(ctx, tree, OID_AUTO, "hold_bus",
452 	    CTLFLAG_RW, &sc->sc_hold, 0,
453 	    "Whether device should hold I2C bus while measuring");
454 	SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "crc_errors",
455 	    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, &sc->sc_errcount, 0,
456 	    "Number of checksum errors");
457 }
458 
459 static int
htu21_probe(device_t dev)460 htu21_probe(device_t dev)
461 {
462 	uint8_t addr;
463 	int rc;
464 
465 #ifdef FDT
466 	if (!ofw_bus_status_okay(dev))
467 		return (ENXIO);
468 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data)
469 		rc = BUS_PROBE_GENERIC;
470 	else
471 #endif
472 		rc = BUS_PROBE_NOWILDCARD;
473 
474 	addr = iicbus_get_addr(dev);
475 	if (addr != (HTU21_ADDR << 1)) {
476 		device_printf(dev, "non-standard slave address 0x%02x\n",
477 		    addr >> 1);
478 	}
479 
480 	device_set_desc(dev, "HTU21 temperature and humidity sensor");
481 	return (rc);
482 }
483 
484 static int
htu21_attach(device_t dev)485 htu21_attach(device_t dev)
486 {
487 	struct htu21_softc *sc;
488 
489 	sc = device_get_softc(dev);
490 	sc->sc_dev = dev;
491 	sc->sc_addr = iicbus_get_addr(dev);
492 
493 	/*
494 	 * We have to wait until interrupts are enabled.  Usually I2C read
495 	 * and write only works when the interrupts are available.
496 	 */
497 	config_intrhook_oneshot(htu21_start, sc);
498 	return (0);
499 }
500 
501 static int
htu21_detach(device_t dev)502 htu21_detach(device_t dev)
503 {
504 	return (0);
505 }
506 
507 static device_method_t  htu21_methods[] = {
508 	/* Device interface */
509 	DEVMETHOD(device_probe,		htu21_probe),
510 	DEVMETHOD(device_attach,	htu21_attach),
511 	DEVMETHOD(device_detach,	htu21_detach),
512 
513 	DEVMETHOD_END
514 };
515 
516 static driver_t htu21_driver = {
517 	"htu21",
518 	htu21_methods,
519 	sizeof(struct htu21_softc)
520 };
521 
522 DRIVER_MODULE(htu21, iicbus, htu21_driver, 0, 0);
523 MODULE_DEPEND(htu21, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
524 MODULE_VERSION(htu21, 1);
525 IICBUS_FDT_PNP_INFO(compat_data);
526