FreeBSD kernel IICBUS device code
htu21.c
Go to the documentation of this file.
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
77 device_t sc_dev;
78 uint32_t sc_addr;
79 uint8_t sc_serial[8];
81 bool sc_hold;
82};
83
84#ifdef FDT
85static struct ofw_compat_data compat_data[] = {
86 { "meas,htu21", true },
87 { NULL, false }
88};
89#endif
90
91static uint8_t
92calc_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
107static int
108check_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
116static int
117check_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
125static int
126htu21_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;
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
148static int
149htu21_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
184static int
185htu21_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)
198 raw_data, nitems(raw_data));
199 else
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
219static int
220htu21_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)
233 raw_data, nitems(raw_data));
234 else
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
254static int
255htu21_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;
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
279static int
280htu21_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
300static int
301htu21_heater_sysctl(SYSCTL_HANDLER_ARGS)
302{
303 device_t dev;
304 uint8_t cfg;
305 int error, heater;
306
307 dev = arg1;
308
309 if (req->oldptr != NULL) {
310 error = htu21_get_cfg(dev, &cfg);
311 if (error != 0)
312 return (EIO);
313 heater = (cfg & 0x04) != 0;
314 }
315 error = sysctl_handle_int(oidp, &heater, 0, req);
316 if (error != 0 || req->newptr == NULL)
317 return (error);
318
319 cfg &= ~0x04;
320 cfg |= (heater > 0) << 2;
321 error = htu21_set_cfg(dev, cfg);
322 return (error != 0 ? EIO : 0);
323}
324
325static int
326htu21_power_sysctl(SYSCTL_HANDLER_ARGS)
327{
328 device_t dev;
329 uint8_t cfg;
330 int error, power;
331
332 dev = arg1;
333
334 if (req->oldptr != NULL) {
335 error = htu21_get_cfg(dev, &cfg);
336 if (error != 0)
337 return (EIO);
338 power = (cfg & 0x40) == 0;
339 }
340 error = sysctl_handle_int(oidp, &power, 0, req);
341 return (error);
342}
343
344/*
345 * May be incompatible with some chips like SHT21 and Si7021.
346 */
347static int
349{
350
351 struct iic_msg msgs[2];
352 struct htu21_softc *sc;
353 uint8_t data[8];
354 uint8_t cmd[2];
355 int error, cksum_err;
356 int i;
357
358 sc = device_get_softc(dev);
359 cmd[0] = HTU2x_SERIAL0_0;
360 cmd[1] = HTU2x_SERIAL0_1;
361 msgs[0].slave = sc->sc_addr;
363 msgs[0].len = nitems(cmd);
364 msgs[0].buf = cmd;
365
366 msgs[1].slave = sc->sc_addr;
367 msgs[1].flags = IIC_M_RD;
368 msgs[1].len = nitems(data);
369 msgs[1].buf = data;
370
371 error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
372 if (error != 0)
373 return (EIO);
374
375 cksum_err = 0;
376 for (i = 0; i < nitems(data); i += 2) {
377 if (!check_crc_8(data[i], data[i + 1]))
378 cksum_err = EINVAL;
379 sc->sc_serial[2 + i / 2] = data[i];
380 }
381
382 cmd[0] = HTU2x_SERIAL1_0;
383 cmd[1] = HTU2x_SERIAL1_1;
384 msgs[0].slave = sc->sc_addr;
386 msgs[0].len = nitems(cmd);
387 msgs[0].buf = cmd;
388
389 msgs[1].slave = sc->sc_addr;
390 msgs[1].flags = IIC_M_RD;
391 msgs[1].len = 6;
392 msgs[1].buf = data;
393
394 error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
395 if (error != 0)
396 return (EIO);
397
398 if (!check_crc_16(&data[0], data[2]))
399 cksum_err = EINVAL;
400 sc->sc_serial[6] = data[0];
401 sc->sc_serial[7] = data[1];
402
403 if (!check_crc_16(&data[3], data[5]))
404 cksum_err = EINVAL;
405 sc->sc_serial[0] = data[3];
406 sc->sc_serial[1] = data[4];
407
408 return (cksum_err);
409}
410
411static void
412htu21_start(void *arg)
413{
414 device_t dev;
415 struct htu21_softc *sc;
416 struct sysctl_ctx_list *ctx;
417 struct sysctl_oid *tree_node;
418 struct sysctl_oid_list *tree;
419 int error;
420
421 sc = arg;
422 dev = sc->sc_dev;
423
424 for (int i = 0; i < 5; i++) {
425 error = htu21_get_serial(dev);
426 if (error == 0)
427 break;
428 }
429 if (error != EIO) {
430 device_printf(dev, "serial number: %8D (checksum %scorrect)\n",
431 sc->sc_serial, ":", error == 0 ? "" : "in");
432 } else {
433 device_printf(dev, "failed to get serial number, err = %d\n",
434 error);
435 }
436
437 ctx = device_get_sysctl_ctx(dev);
438 tree_node = device_get_sysctl_tree(dev);
439 tree = SYSCTL_CHILDREN(tree_node);
440
441 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "temperature",
442 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
443 htu21_temp_sysctl, "IK2", "Current temperature");
444 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "humidity",
445 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
446 htu21_rh_sysctl, "I", "Relative humidity in 0.01%% units");
447 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "heater",
448 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, 0,
449 htu21_heater_sysctl, "IU", "Enable built-in heater");
450 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "power",
451 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
452 htu21_power_sysctl, "IU", "If sensor's power is good");
453 SYSCTL_ADD_BOOL(ctx, tree, OID_AUTO, "hold_bus",
454 CTLFLAG_RW, &sc->sc_hold, 0,
455 "Whether device should hold I2C bus while measuring");
456 SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "crc_errors",
457 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, &sc->sc_errcount, 0,
458 "Number of checksum errors");
459}
460
461static int
463{
464 uint8_t addr;
465 int rc;
466
467#ifdef FDT
468 if (!ofw_bus_status_okay(dev))
469 return (ENXIO);
470 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data)
471 rc = BUS_PROBE_GENERIC;
472 else
473#endif
474 rc = BUS_PROBE_NOWILDCARD;
475
476 addr = iicbus_get_addr(dev);
477 if (addr != (HTU21_ADDR << 1)) {
478 device_printf(dev, "non-standard slave address 0x%02x\n",
479 addr >> 1);
480 }
481
482 device_set_desc(dev, "HTU21 temperature and humidity sensor");
483 return (rc);
484}
485
486static int
488{
489 struct htu21_softc *sc;
490
491 sc = device_get_softc(dev);
492 sc->sc_dev = dev;
493 sc->sc_addr = iicbus_get_addr(dev);
494
495 /*
496 * We have to wait until interrupts are enabled. Usually I2C read
497 * and write only works when the interrupts are available.
498 */
499 config_intrhook_oneshot(htu21_start, sc);
500 return (0);
501}
502
503static int
505{
506 return (0);
507}
508
509static device_method_t htu21_methods[] = {
510 /* Device interface */
511 DEVMETHOD(device_probe, htu21_probe),
512 DEVMETHOD(device_attach, htu21_attach),
513 DEVMETHOD(device_detach, htu21_detach),
514
515 DEVMETHOD_END
516};
517
518static driver_t htu21_driver = {
519 "htu21",
521 sizeof(struct htu21_softc)
522};
523
524static devclass_t htu21_devclass;
525
#define IIC_M_WR
Definition: ad7418.c:45
static ds13_compat_data compat_data[]
Definition: ds13rtc.c:187
static int htu21_temp_sysctl(SYSCTL_HANDLER_ARGS)
Definition: htu21.c:185
static uint8_t calc_crc(uint16_t data)
Definition: htu21.c:92
static int htu21_heater_sysctl(SYSCTL_HANDLER_ARGS)
Definition: htu21.c:301
static void htu21_start(void *arg)
Definition: htu21.c:412
#define HTU2x_SERIAL1_1
Definition: htu21.c:74
static int htu21_get_measurement_nohold(device_t dev, uint8_t cmd, uint8_t *data, int count)
Definition: htu21.c:149
static int htu21_get_serial(device_t dev)
Definition: htu21.c:348
static int htu21_probe(device_t dev)
Definition: htu21.c:462
static driver_t htu21_driver
Definition: htu21.c:518
MODULE_VERSION(htu21, 1)
MODULE_DEPEND(htu21, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER)
static int check_crc_8(const uint8_t data, uint8_t expected)
Definition: htu21.c:117
#define HTU21_GET_HUM
Definition: htu21.c:64
static int htu21_attach(device_t dev)
Definition: htu21.c:487
static device_method_t htu21_methods[]
Definition: htu21.c:509
#define HTU21_GET_TEMP
Definition: htu21.c:63
#define HTU21_GET_TEMP_NH
Definition: htu21.c:65
DRIVER_MODULE(htu21, iicbus, htu21_driver, htu21_devclass, 0, 0)
#define HTU2x_SERIAL1_0
Definition: htu21.c:73
static int htu21_get_cfg(device_t dev, uint8_t *cfg)
Definition: htu21.c:255
#define HTU2x_SERIAL0_1
Definition: htu21.c:72
__FBSDID("$FreeBSD$")
#define HTU2x_SERIAL0_0
Definition: htu21.c:71
static int htu21_power_sysctl(SYSCTL_HANDLER_ARGS)
Definition: htu21.c:326
static devclass_t htu21_devclass
Definition: htu21.c:524
static int htu21_detach(device_t dev)
Definition: htu21.c:504
#define HTU21_WRITE_CFG
Definition: htu21.c:67
#define HTU21_GET_HUM_NH
Definition: htu21.c:66
static int htu21_get_measurement(device_t dev, uint8_t cmd, uint8_t *data, int count)
Definition: htu21.c:126
IICBUS_FDT_PNP_INFO(compat_data)
static int htu21_set_cfg(device_t dev, uint8_t cfg)
Definition: htu21.c:280
static int htu21_rh_sysctl(SYSCTL_HANDLER_ARGS)
Definition: htu21.c:220
static int check_crc_16(const uint8_t *data, uint8_t expected)
Definition: htu21.c:108
#define HTU21_READ_CFG
Definition: htu21.c:68
#define HTU21_ADDR
Definition: htu21.c:61
#define IIC_M_RD
Definition: iic.h:42
#define IIC_M_NOSTOP
Definition: iic.h:43
caddr_t data
Definition: iicbb_if.m:61
u_char addr
Definition: iicbb_if.m:116
char * buf
Definition: iicbus_if.m:55
struct iic_msg * msgs
Definition: iicbus_if.m:134
INTERFACE iicbus
Definition: iicbus_if.m:32
int iicbus_transfer_excl(device_t dev, struct iic_msg *msgs, uint32_t nmsgs, int how)
Definition: iiconf.c:449
#define IICBUS_MINVER
Definition: iiconf.h:171
#define IIC_INTRWAIT
Definition: iiconf.h:48
#define IICBUS_MAXVER
Definition: iiconf.h:172
#define IICBUS_PREFVER
Definition: iiconf.h:173
#define IIC_ENOACK
Definition: iiconf.h:109
device_t dev
Definition: ofw_iicbus_if.m:38
int sc_errcount
Definition: htu21.c:80
bool sc_hold
Definition: htu21.c:81
uint32_t sc_addr
Definition: htu21.c:78
uint8_t sc_serial[8]
Definition: htu21.c:79
device_t sc_dev
Definition: htu21.c:77
Definition: iic.h:38
uint16_t slave
Definition: iic.h:39
uint16_t len
Definition: iic.h:45
uint8_t * buf
Definition: iic.h:46
uint16_t flags
Definition: iic.h:40