#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
#define PIN_IDX 0
#define GPIOTHS_POLLTIME 5
#define GPIOTHS_DHT_STARTCYCLE 20000
#define GPIOTHS_DHT_TIMEOUT 1000
#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;
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);
}
return (ETIMEDOUT);
}
static void
gpioths_dht_initread(struct gpioths_softc *sc)
{
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;
}
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;
}
}
avglen = 0;
for (i = 1; i < GPIOTHS_DHT_CYCLES; i++)
avglen += calibrations[i];
avglen = avglen / (GPIOTHS_DHT_CYCLES - 1);
value = 0;
offset = 1;
size = sizeof(value) * 8;
for (i = offset; i < size + offset; i++) {
value <<= 1;
if (intervals[i] > avglen)
value += 1;
}
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
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
if (calc != crc) {
err = -1;
goto error;
}
#define DK_OFFSET 2731
if ((value >> 24) > 10) {
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 {
negmul = (value & 0x8000) ? -1 : 1;
sc->temp = DK_OFFSET + (negmul * (value & 0x03ff));
sc->hum = ((value >> 16) & 0x03ff) / 10;
}
sc->fails = 0;
#ifdef GPIOTHS_DEBUG
device_printf(dev, "fails=%d, temp=%d, hum=%d\n", sc->fails,
sc->temp, sc->hum);
#endif
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
err = gpio_pin_get_by_ofw_idx(dev, ofw_bus_get_node(dev), PIN_IDX,
&sc->pin);
#else
err = ENOENT;
#endif
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 (err != 0) {
device_printf(sc->dev,
"cannot acquire gpio pin (config error)\n");
return (err);
}
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);
}
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);
}
static device_method_t gpioths_methods[] = {
DEVMETHOD(device_probe, gpioths_probe),
DEVMETHOD(device_attach, gpioths_attach),
DEVMETHOD(device_detach, gpioths_detach),
DEVMETHOD_END
};
DEFINE_CLASS_0(gpioths, gpioths_driver, gpioths_methods, sizeof(struct gpioths_softc));
#ifdef FDT
DRIVER_MODULE(gpioths, simplebus, gpioths_driver, 0, 0);
#endif
DRIVER_MODULE(gpioths, gpiobus, gpioths_driver, 0, 0);
MODULE_DEPEND(gpioths, gpiobus, 1, 1, 1);