]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/iicbus/htu21.c
Import atf 0.22 snapshot 55c21b2c5fb189bbdfccb2b297bfa89236502542
[FreeBSD/FreeBSD.git] / sys / dev / iicbus / htu21.c
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
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
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
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
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
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
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 (sc->sc_hold)
196                 error = htu21_get_measurement(dev, HTU21_GET_TEMP,
197                     raw_data, nitems(raw_data));
198         else
199                 error = htu21_get_measurement_nohold(dev, HTU21_GET_TEMP_NH,
200                     raw_data, nitems(raw_data));
201
202         if (error != 0) {
203                 return (EIO);
204         } else if (!check_crc_16(raw_data, raw_data[2])) {
205                 temp = -1;
206                 sc->sc_errcount++;
207         } else {
208                 temp = (((uint16_t)raw_data[0]) << 8) | (raw_data[1] & 0xfc);
209                 temp = ((temp * 17572) >> 16 ) + 27315 - 4685;
210         }
211
212         error = sysctl_handle_int(oidp, &temp, 0, req);
213         return (error);
214 }
215
216 static int
217 htu21_rh_sysctl(SYSCTL_HANDLER_ARGS)
218 {
219         struct htu21_softc *sc;
220         device_t dev;
221         uint8_t raw_data[3];
222         int error, rh;
223
224         dev = arg1;
225         sc = device_get_softc(dev);
226
227         if (sc->sc_hold)
228                 error = htu21_get_measurement(dev, HTU21_GET_HUM,
229                     raw_data, nitems(raw_data));
230         else
231                 error = htu21_get_measurement_nohold(dev, HTU21_GET_HUM_NH,
232                     raw_data, nitems(raw_data));
233
234         if (error != 0) {
235                 return (EIO);
236         } else if (!check_crc_16(raw_data, raw_data[2])) {
237                 rh = -1;
238                 sc->sc_errcount++;
239         } else {
240                 rh = (((uint16_t)raw_data[0]) << 8) | (raw_data[1] & 0xfc);
241                 rh = ((rh * 12500) >> 16 ) - 600;
242         }
243
244         error = sysctl_handle_int(oidp, &rh, 0, req);
245         return (error);
246 }
247
248 static int
249 htu21_get_cfg(device_t dev, uint8_t *cfg)
250 {
251
252         struct iic_msg msgs[2];
253         struct htu21_softc *sc;
254         uint8_t cmd;
255         int error;
256
257         sc = device_get_softc(dev);
258         cmd = HTU21_READ_CFG;
259         msgs[0].slave = sc->sc_addr;
260         msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
261         msgs[0].len = 1;
262         msgs[0].buf = &cmd;
263
264         msgs[1].slave = sc->sc_addr;
265         msgs[1].flags = IIC_M_RD;
266         msgs[1].len = 1;
267         msgs[1].buf = cfg;
268
269         error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
270         return (error);
271 }
272
273 static int
274 htu21_set_cfg(device_t dev, uint8_t cfg)
275 {
276
277         struct iic_msg msg;
278         struct htu21_softc *sc;
279         uint8_t buf[2];
280         int error;
281
282         sc = device_get_softc(dev);
283         buf[0] = HTU21_WRITE_CFG;
284         buf[1] = cfg;
285         msg.slave = sc->sc_addr;
286         msg.flags = IIC_M_WR;
287         msg.len = 2;
288         msg.buf = buf;
289
290         error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT);
291         return (error);
292 }
293
294 static int
295 htu21_heater_sysctl(SYSCTL_HANDLER_ARGS)
296 {
297         struct htu21_softc *sc;
298         device_t dev;
299         uint8_t cfg;
300         int error, heater;
301
302         dev = arg1;
303         sc = device_get_softc(dev);
304
305         error = htu21_get_cfg(dev, &cfg);
306         if (error != 0)
307                 return (EIO);
308
309         heater = (cfg & 0x04) != 0;
310         error = sysctl_handle_int(oidp, &heater, 0, req);
311         if (error != 0 || req->newptr == NULL)
312                 return (error);
313
314         cfg &= ~0x04;
315         cfg |= (heater > 0) << 2;
316         error = htu21_set_cfg(dev, cfg);
317         return (error != 0 ? EIO : 0);
318 }
319
320 static int
321 htu21_power_sysctl(SYSCTL_HANDLER_ARGS)
322 {
323         struct htu21_softc *sc;
324         device_t dev;
325         uint8_t cfg;
326         int error, power;
327
328         dev = arg1;
329         sc = device_get_softc(dev);
330
331         error = htu21_get_cfg(dev, &cfg);
332         if (error != 0)
333                 return (EIO);
334
335         power = (cfg & 0x40) == 0;
336         error = sysctl_handle_int(oidp, &power, 0, req);
337         return (error);
338 }
339
340 /*
341  * May be incompatible with some chips like SHT21 and Si7021.
342  */
343 static int
344 htu21_get_serial(device_t dev)
345 {
346
347         struct iic_msg msgs[2];
348         struct htu21_softc *sc;
349         uint8_t data[8];
350         uint8_t cmd[2];
351         int error, cksum_err;
352         int i;
353
354         sc = device_get_softc(dev);
355         cmd[0] = HTU2x_SERIAL0_0;
356         cmd[1] = HTU2x_SERIAL0_1;
357         msgs[0].slave = sc->sc_addr;
358         msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
359         msgs[0].len = nitems(cmd);
360         msgs[0].buf = cmd;
361
362         msgs[1].slave = sc->sc_addr;
363         msgs[1].flags = IIC_M_RD;
364         msgs[1].len = nitems(data);
365         msgs[1].buf = data;
366
367         error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
368         if (error != 0)
369                 return (EIO);
370
371         cksum_err = 0;
372         for (i = 0; i < nitems(data); i += 2) {
373                 if (!check_crc_8(data[i], data[i + 1]))
374                         cksum_err = EINVAL;
375                 sc->sc_serial[2 + i / 2] = data[i];
376         }
377
378         cmd[0] = HTU2x_SERIAL1_0;
379         cmd[1] = HTU2x_SERIAL1_1;
380         msgs[0].slave = sc->sc_addr;
381         msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
382         msgs[0].len = nitems(cmd);
383         msgs[0].buf = cmd;
384
385         msgs[1].slave = sc->sc_addr;
386         msgs[1].flags = IIC_M_RD;
387         msgs[1].len = 6;
388         msgs[1].buf = data;
389
390         error = iicbus_transfer_excl(dev, msgs, nitems(msgs), IIC_INTRWAIT);
391         if (error != 0)
392                 return (EIO);
393
394         if (!check_crc_16(&data[0], data[2]))
395                 cksum_err = EINVAL;
396         sc->sc_serial[6] = data[0];
397         sc->sc_serial[7] = data[1];
398
399         if (!check_crc_16(&data[3], data[5]))
400                 cksum_err = EINVAL;
401         sc->sc_serial[0] = data[3];
402         sc->sc_serial[1] = data[4];
403
404         return (cksum_err);
405 }
406
407 static void
408 htu21_start(void *arg)
409 {
410         device_t dev;
411         struct htu21_softc *sc;
412         struct sysctl_ctx_list *ctx;
413         struct sysctl_oid *tree_node;
414         struct sysctl_oid_list *tree;
415         int error;
416
417         sc = arg;
418         dev = sc->sc_dev;
419
420         for (int i = 0; i < 5; i++) {
421                 error = htu21_get_serial(dev);
422                 if (error == 0)
423                         break;
424         }
425         if (error != EIO) {
426                 device_printf(dev, "serial number: %8D (checksum %scorrect)\n",
427                     sc->sc_serial, ":", error == 0 ? "" : "in");
428         } else {
429                 device_printf(dev, "failed to get serial number, err = %d\n",
430                     error);
431         }
432
433         ctx = device_get_sysctl_ctx(dev);
434         tree_node = device_get_sysctl_tree(dev);
435         tree = SYSCTL_CHILDREN(tree_node);
436
437         SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "temperature",
438             CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
439             htu21_temp_sysctl, "IK2", "Current temperature");
440         SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "humidity",
441             CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
442             htu21_rh_sysctl, "I", "Relative humidity in 0.01%% units");
443         SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "heater",
444             CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, 0,
445             htu21_heater_sysctl, "IU", "Enable built-in heater");
446         SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "power",
447             CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
448             htu21_power_sysctl, "IU", "If sensor's power is good");
449         SYSCTL_ADD_BOOL(ctx, tree, OID_AUTO, "hold_bus",
450             CTLFLAG_RW, &sc->sc_hold, 0,
451             "Whether device should hold I2C bus while measuring");
452         SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "crc_errors",
453             CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, &sc->sc_errcount, 0,
454             "Number of checksum errors");
455 }
456
457 static int
458 htu21_probe(device_t dev)
459 {
460         uint8_t addr;
461
462 #ifdef FDT
463         if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
464                 return (ENXIO);
465 #endif
466         addr = iicbus_get_addr(dev);
467         if (addr != (HTU21_ADDR << 1)) {
468                 device_printf(dev, "non-standard slave address 0x%02x\n",
469                     addr >> 1);
470         }
471
472         device_set_desc(dev, "HTU21 temperature and humidity sensor");
473         return (BUS_PROBE_GENERIC);
474 }
475
476 static int
477 htu21_attach(device_t dev)
478 {
479         struct htu21_softc *sc;
480
481         sc = device_get_softc(dev);
482         sc->sc_dev = dev;
483         sc->sc_addr = iicbus_get_addr(dev);
484
485         /*
486          * We have to wait until interrupts are enabled.  Usually I2C read
487          * and write only works when the interrupts are available.
488          */
489         config_intrhook_oneshot(htu21_start, sc);
490         return (0);
491 }
492
493 static int
494 htu21_detach(device_t dev)
495 {
496         return (0);
497 }
498
499 static device_method_t  htu21_methods[] = {
500         /* Device interface */
501         DEVMETHOD(device_probe,         htu21_probe),
502         DEVMETHOD(device_attach,        htu21_attach),
503         DEVMETHOD(device_detach,        htu21_detach),
504
505         DEVMETHOD_END
506 };
507
508 static driver_t htu21_driver = {
509         "htu21",
510         htu21_methods,
511         sizeof(struct htu21_softc)
512 };
513
514 static devclass_t htu21_devclass;
515
516 DRIVER_MODULE(htu21, iicbus, htu21_driver, htu21_devclass, 0, 0);
517 MODULE_DEPEND(htu21, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
518 MODULE_VERSION(htu21, 1);
519 #ifdef FDT
520 IICBUS_FDT_PNP_INFO(compat_data);
521 #endif