freebsd-dev/sys/dev/gpio/gpioths.c
Mark Johnston 519b64e27f Revert "Define PNP info after defining driver modules"
This reverts commit aa37baf3d7.

The reverted commit was motivated by a problem observed on stable/12,
but it turns out that a better solution was committed in r348309 but not
MFCed.  So, revert this change since it is unnecessary and not really
correct: it assumes that the order in which module metadata records is
defined determines their order in the output linker set.  While this
seems to hold in my testing, it is not guaranteed.

Reported by:	cem
Discussed with:	imp
MFC after:	3 days
2021-01-23 10:59:41 -05:00

418 lines
12 KiB
C

/*-
* 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>
__FBSDID("$FreeBSD$");
#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);