The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/dev/iicbus/htu21.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    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 (req->oldptr != NULL) {
  196                 if (sc->sc_hold)
  197                         error = htu21_get_measurement(dev, HTU21_GET_TEMP,
  198                             raw_data, nitems(raw_data));
  199                 else
  200                         error = htu21_get_measurement_nohold(dev,
  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 
  219 static int
  220 htu21_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)
  232                         error = htu21_get_measurement(dev, HTU21_GET_HUM,
  233                             raw_data, nitems(raw_data));
  234                 else
  235                         error = htu21_get_measurement_nohold(dev,
  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 
  254 static int
  255 htu21_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;
  266         msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
  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 
  279 static int
  280 htu21_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 
  300 static int
  301 htu21_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 
  325 static int
  326 htu21_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  */
  347 static int
  348 htu21_get_serial(device_t dev)
  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;
  362         msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
  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;
  385         msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
  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 
  411 static void
  412 htu21_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 
  461 static int
  462 htu21_probe(device_t dev)
  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 
  486 static int
  487 htu21_attach(device_t dev)
  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 
  503 static int
  504 htu21_detach(device_t dev)
  505 {
  506         return (0);
  507 }
  508 
  509 static 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 
  518 static driver_t htu21_driver = {
  519         "htu21",
  520         htu21_methods,
  521         sizeof(struct htu21_softc)
  522 };
  523 
  524 DRIVER_MODULE(htu21, iicbus, htu21_driver, 0, 0);
  525 MODULE_DEPEND(htu21, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
  526 MODULE_VERSION(htu21, 1);
  527 IICBUS_FDT_PNP_INFO(compat_data);

Cache object: bb86e1209e3c1a151e797a9e1f294678


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.