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