1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
|
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2016 Michael Zhilin <mizhka@freebsd.org> All rights reserved.
* Copyright (c) 2019 Ian Lepore <ian@freebsd.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* GPIOTHS - Temp/Humidity sensor over GPIO.
*
* This is driver for Temperature & Humidity sensor which provides digital
* output over single-wire protocol from embedded 8-bit microcontroller.
* Note that it uses a custom single-wire protocol, it is not 1-wire(tm).
*
* This driver supports the following chips:
* DHT11: Temp 0c to 50c +-2.0c, Humidity 20% to 90% +-5%
* DHT12: Temp -20c to 60c +-0.5c, Humidity 20% to 95% +-5%
* DHT21: Temp -40c to 80c +-0.3c, Humidity 0% to 100% +-3%
* DHT22: Temp -40c to 80c +-0.3c, Humidity 0% to 100% +-2%
* AM2301: Same as DHT21, but also supports i2c interface.
* AM2302: Same as DHT22, but also supports i2c interface.
*
* Temp/Humidity sensor can't be discovered automatically, please specify hints
* as part of loader or kernel configuration:
* hint.gpioths.0.at="gpiobus0"
* hint.gpioths.0.pins=<PIN>
*
* Or configure via FDT data.
*/
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/gpio.h>
#include <sys/module.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/taskqueue.h>
#include <dev/gpio/gpiobusvar.h>
#ifdef FDT
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
static struct ofw_compat_data compat_data[] = {
{"dht11", true},
{NULL, false}
};
OFWBUS_PNP_INFO(compat_data);
SIMPLEBUS_PNP_INFO(compat_data);
#endif /* FDT */
#define PIN_IDX 0 /* Use the first/only configured pin. */
#define GPIOTHS_POLLTIME 5 /* in seconds */
#define GPIOTHS_DHT_STARTCYCLE 20000 /* 20ms = 20000us */
#define GPIOTHS_DHT_TIMEOUT 1000 /* 1ms = 1000us */
#define GPIOTHS_DHT_CYCLES 41
#define GPIOTHS_DHT_ONEBYTEMASK 0xFF
struct gpioths_softc {
device_t dev;
gpio_pin_t pin;
int temp;
int hum;
int fails;
struct timeout_task task;
bool detaching;
};
static int
gpioths_probe(device_t dev)
{
int rv;
/*
* By default we only bid to attach if specifically added by our parent
* (usually via hint.gpioths.#.at=busname). On FDT systems we bid as
* the default driver based on being configured in the FDT data.
*/
rv = BUS_PROBE_NOWILDCARD;
#ifdef FDT
if (ofw_bus_status_okay(dev) &&
ofw_bus_search_compatible(dev, compat_data)->ocd_data)
rv = BUS_PROBE_DEFAULT;
#endif
device_set_desc(dev, "DHT11/DHT22 Temperature and Humidity Sensor");
return (rv);
}
static int
gpioths_dht_timeuntil(struct gpioths_softc *sc, bool lev, uint32_t *time)
{
bool cur_level;
int i;
for (i = 0; i < GPIOTHS_DHT_TIMEOUT; i++) {
gpio_pin_is_active(sc->pin, &cur_level);
if (cur_level == lev) {
if (time != NULL)
*time = i;
return (0);
}
DELAY(1);
}
/* Timeout */
return (ETIMEDOUT);
}
static void
gpioths_dht_initread(struct gpioths_softc *sc)
{
/*
* According to specifications we need to drive the data line low for at
* least 20ms then drive it high, to wake up the chip and signal it to
* send a measurement. After sending this start signal, we switch the
* pin back to input so the device can begin talking to us.
*/
gpio_pin_setflags(sc->pin, GPIO_PIN_OUTPUT);
gpio_pin_set_active(sc->pin, false);
pause_sbt("gpioths", ustosbt(GPIOTHS_DHT_STARTCYCLE), C_PREL(2), 0);
gpio_pin_set_active(sc->pin, true);
gpio_pin_setflags(sc->pin, GPIO_PIN_INPUT);
}
static int
gpioths_dht_readbytes(struct gpioths_softc *sc)
{
uint32_t calibrations[GPIOTHS_DHT_CYCLES];
uint32_t intervals[GPIOTHS_DHT_CYCLES];
uint32_t err, avglen, value;
uint8_t crc, calc;
int i, negmul, offset, size, tmphi, tmplo;
gpioths_dht_initread(sc);
err = gpioths_dht_timeuntil(sc, false, NULL);
if (err) {
device_printf(sc->dev, "err(START) = %d\n", err);
goto error;
}
/* reading - 41 cycles */
for (i = 0; i < GPIOTHS_DHT_CYCLES; i++) {
err = gpioths_dht_timeuntil(sc, true, &calibrations[i]);
if (err) {
device_printf(sc->dev, "err(CAL, %d) = %d\n", i, err);
goto error;
}
err = gpioths_dht_timeuntil(sc, false, &intervals[i]);
if (err) {
device_printf(sc->dev, "err(INTERVAL, %d) = %d\n", i, err);
goto error;
}
}
/* Calculate average data calibration cycle length */
avglen = 0;
for (i = 1; i < GPIOTHS_DHT_CYCLES; i++)
avglen += calibrations[i];
avglen = avglen / (GPIOTHS_DHT_CYCLES - 1);
/* Calculate data */
value = 0;
offset = 1;
size = sizeof(value) * 8;
for (i = offset; i < size + offset; i++) {
value <<= 1;
if (intervals[i] > avglen)
value += 1;
}
/* Calculate CRC */
crc = 0;
offset = sizeof(value) * 8 + 1;
size = sizeof(crc) * 8;
for (i = offset; i < size + offset; i++) {
crc <<= 1;
if (intervals[i] > avglen)
crc += 1;
}
calc = 0;
for (i = 0; i < sizeof(value); i++)
calc += (value >> (8*i)) & GPIOTHS_DHT_ONEBYTEMASK;
#ifdef GPIOTHS_DEBUG
/* Debug bits */
for (i = 0; i < GPIOTHS_DHT_CYCLES; i++)
device_printf(sc->dev, "%d: %d %d\n", i, calibrations[i],
intervals[i]);
device_printf(sc->dev, "len=%d, data=%x, crc=%x/%x\n", avglen, value, crc,
calc);
#endif /* GPIOTHS_DEBUG */
/* CRC check */
if (calc != crc) {
err = -1;
goto error;
}
/*
* For DHT11/12, the values are split into 8 bits of integer and 8 bits
* of fractional tenths. On DHT11 the fraction bytes are always zero.
* On DHT12 the sign bit is in the high bit of the fraction byte.
* - DHT11: 0HHHHHHH 00000000 00TTTTTT 00000000
* - DHT12: 0HHHHHHH 0000hhhh 00TTTTTT s000tttt
*
* For DHT21/21, the values are are encoded in 16 bits each, with the
* temperature sign bit in the high bit. The values are tenths of a
* degree C and tenths of a percent RH.
* - DHT21: 000000HH HHHHHHHH s00000TT TTTTTTTT
* - DHT22: 000000HH HHHHHHHH s00000TT TTTTTTTT
*
* For all devices, some bits are always zero because of the range of
* values supported by the device.
*
* We figure out how to decode things based on the high byte of the
* humidity. A DHT21/22 cannot report a value greater than 3 in
* the upper bits of its 16-bit humidity. A DHT11/12 should not report
* a value lower than 20. To allow for the possibility that a device
* could report a value slightly out of its sensitivity range, we split
* the difference and say if the value is greater than 10 it must be a
* DHT11/12 (that would be a humidity over 256% on a DHT21/22).
*/
#define DK_OFFSET 2731 /* Offset between K and C, in decikelvins. */
if ((value >> 24) > 10) {
/* DHT11 or DHT12 */
tmphi = (value >> 8) & 0x3f;
tmplo = value & 0x0f;
negmul = (value & 0x80) ? -1 : 1;
sc->temp = DK_OFFSET + (negmul * (tmphi * 10 + tmplo));
sc->hum = (value >> 24) & 0x7f;
} else {
/* DHT21 or DHT22 */
negmul = (value & 0x8000) ? -1 : 1;
sc->temp = DK_OFFSET + (negmul * (value & 0x03ff));
sc->hum = ((value >> 16) & 0x03ff) / 10;
}
sc->fails = 0;
#ifdef GPIOTHS_DEBUG
/* Debug bits */
device_printf(dev, "fails=%d, temp=%d, hum=%d\n", sc->fails,
sc->temp, sc->hum);
#endif /* GPIOTHS_DEBUG */
return (0);
error:
sc->fails++;
return (err);
}
static void
gpioths_poll(void *arg, int pending __unused)
{
struct gpioths_softc *sc;
sc = (struct gpioths_softc *)arg;
gpioths_dht_readbytes(sc);
if (!sc->detaching)
taskqueue_enqueue_timeout_sbt(taskqueue_thread, &sc->task,
GPIOTHS_POLLTIME * SBT_1S, 0, C_PREL(3));
}
static int
gpioths_attach(device_t dev)
{
struct gpioths_softc *sc;
struct sysctl_ctx_list *ctx;
struct sysctl_oid *tree;
int err;
sc = device_get_softc(dev);
ctx = device_get_sysctl_ctx(dev);
tree = device_get_sysctl_tree(dev);
sc->dev = dev;
TIMEOUT_TASK_INIT(taskqueue_thread, &sc->task, 0, gpioths_poll, sc);
#ifdef FDT
/* Try to configure our pin from fdt data on fdt-based systems. */
err = gpio_pin_get_by_ofw_idx(dev, ofw_bus_get_node(dev), PIN_IDX,
&sc->pin);
#else
err = ENOENT;
#endif
/*
* If we didn't get configured by fdt data and our parent is gpiobus,
* see if we can be configured by the bus (allows hinted attachment even
* on fdt-based systems).
*/
if (err != 0 &&
strcmp("gpiobus", device_get_name(device_get_parent(dev))) == 0)
err = gpio_pin_get_by_child_index(dev, PIN_IDX, &sc->pin);
/* If we didn't get configured by either method, whine and punt. */
if (err != 0) {
device_printf(sc->dev,
"cannot acquire gpio pin (config error)\n");
return (err);
}
/*
* Ensure we have control of our pin, and preset the data line to its
* idle condition (high). Leave the line in input mode, relying on the
* external pullup to keep the line high while idle.
*/
err = gpio_pin_setflags(sc->pin, GPIO_PIN_OUTPUT);
if (err != 0) {
device_printf(dev, "gpio_pin_setflags(OUT) = %d\n", err);
return (err);
}
err = gpio_pin_set_active(sc->pin, true);
if (err != 0) {
device_printf(dev, "gpio_pin_set_active(false) = %d\n", err);
return (err);
}
err = gpio_pin_setflags(sc->pin, GPIO_PIN_INPUT);
if (err != 0) {
device_printf(dev, "gpio_pin_setflags(IN) = %d\n", err);
return (err);
}
/*
* Do an initial read so we have correct values for reporting before
* registering the sysctls that can access those values. This also
* schedules the periodic polling the driver does every few seconds to
* update the sysctl variables.
*/
gpioths_poll(sc, 0);
sysctl_add_oid(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "temperature", \
CTLFLAG_RD | CTLTYPE_INT | CTLFLAG_MPSAFE,
&sc->temp, 0, sysctl_handle_int, "IK", "temperature", NULL);
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "humidity",
CTLFLAG_RD, &sc->hum, 0, "relative humidity(%)");
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "fails",
CTLFLAG_RD, &sc->fails, 0,
"failures since last successful read");
return (0);
}
static int
gpioths_detach(device_t dev)
{
struct gpioths_softc *sc;
sc = device_get_softc(dev);
gpio_pin_release(sc->pin);
sc->detaching = true;
while (taskqueue_cancel_timeout(taskqueue_thread, &sc->task, NULL) != 0)
taskqueue_drain_timeout(taskqueue_thread, &sc->task);
return (0);
}
/* Driver bits */
static device_method_t gpioths_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, gpioths_probe),
DEVMETHOD(device_attach, gpioths_attach),
DEVMETHOD(device_detach, gpioths_detach),
DEVMETHOD_END
};
static devclass_t gpioths_devclass;
DEFINE_CLASS_0(gpioths, gpioths_driver, gpioths_methods, sizeof(struct gpioths_softc));
#ifdef FDT
DRIVER_MODULE(gpioths, simplebus, gpioths_driver, gpioths_devclass, 0, 0);
#endif
DRIVER_MODULE(gpioths, gpiobus, gpioths_driver, gpioths_devclass, 0, 0);
MODULE_DEPEND(gpioths, gpiobus, 1, 1, 1);
|