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
74struct 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
83static struct ofw_compat_data compat_data[] = {
84	{ "meas,htu21",		true },
85	{ NULL,			false }
86};
87#endif
88
89static uint8_t
90calc_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
105static int
106check_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
114static int
115check_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
123static int
124htu21_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
146static int
147htu21_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
182static int
183htu21_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
217static int
218htu21_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
252static int
253htu21_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
277static int
278htu21_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
298static int
299htu21_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
323static int
324htu21_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 */
345static int
346htu21_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
409static void
410htu21_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
459static int
460htu21_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
484static int
485htu21_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
501static int
502htu21_detach(device_t dev)
503{
504	return (0);
505}
506
507static 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
516static driver_t htu21_driver = {
517	"htu21",
518	htu21_methods,
519	sizeof(struct htu21_softc)
520};
521
522DRIVER_MODULE(htu21, iicbus, htu21_driver, 0, 0);
523MODULE_DEPEND(htu21, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
524MODULE_VERSION(htu21, 1);
525IICBUS_FDT_PNP_INFO(compat_data);
526