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/gpio/pcf8574.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) 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 /*
   30  * Driver for PCF8574 / PCF8574A 8-bit I/O expander
   31  * with quasi-bidirectional I/O.
   32  * There is no separate mode / configuration register.
   33  * Pins are set and queried via a single register.
   34  * Because of that we have to maintain the state in the driver
   35  * and assume that there is no outside meddling with the device.
   36  * See the datasheet for details.
   37  */
   38 
   39 #include <sys/cdefs.h>
   40 __FBSDID("$FreeBSD$");
   41 
   42 #include "opt_platform.h"
   43 
   44 #include <sys/param.h>
   45 #include <sys/bus.h>
   46 #include <sys/gpio.h>
   47 #include <sys/kernel.h>
   48 #include <sys/module.h>
   49 #include <sys/systm.h>
   50 #include <sys/sx.h>
   51 
   52 #ifdef FDT
   53 #include <dev/ofw/openfirm.h>
   54 #include <dev/ofw/ofw_bus.h>
   55 #include <dev/ofw/ofw_bus_subr.h>
   56 #endif
   57 
   58 #include <dev/iicbus/iicbus.h>
   59 #include <dev/iicbus/iiconf.h>
   60 
   61 #include <dev/gpio/gpiobusvar.h>
   62 
   63 #include "gpio_if.h"
   64 
   65 #define NUM_PINS        8
   66 #define PIN_CAPS        (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT)
   67 
   68 #define dbg_dev_printf(dev, fmt, args...)       \
   69         if (bootverbose) device_printf(dev, fmt, ##args)
   70 
   71 struct pcf8574_softc {
   72         device_t        dev;
   73         device_t        busdev;
   74         struct sx       lock;
   75         uint8_t         addr;
   76         uint8_t         output_mask;
   77         uint8_t         output_state;
   78 };
   79 
   80 #ifdef FDT
   81 static struct ofw_compat_data compat_data[] = {
   82         { "nxp,pcf8574",        1 },
   83         { "nxp,pcf8574a",       1 },
   84         { NULL,                 0 }
   85 };
   86 #endif
   87 
   88 static int
   89 pcf8574_read(struct pcf8574_softc *sc, uint8_t *val)
   90 {
   91         struct iic_msg msg;
   92         int error;
   93 
   94         msg.slave = sc->addr;
   95         msg.flags = IIC_M_RD;
   96         msg.len = 1;
   97         msg.buf = val;
   98 
   99         error = iicbus_transfer_excl(sc->dev, &msg, 1, IIC_WAIT);
  100         return (iic2errno(error));
  101 }
  102 
  103 static int
  104 pcf8574_write(struct pcf8574_softc *sc, uint8_t val)
  105 {
  106         struct iic_msg msg;
  107         int error;
  108 
  109         msg.slave = sc->addr;
  110         msg.flags = IIC_M_WR;
  111         msg.len = 1;
  112         msg.buf = &val;
  113 
  114         error = iicbus_transfer_excl(sc->dev, &msg, 1, IIC_WAIT);
  115         return (iic2errno(error));
  116 }
  117 
  118 static int
  119 pcf8574_probe(device_t dev)
  120 {
  121 
  122 #ifdef FDT
  123         if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
  124                 return (ENXIO);
  125 #endif
  126         device_set_desc(dev, "PCF8574 I/O expander");
  127         return (BUS_PROBE_DEFAULT);
  128 }
  129 
  130 static int
  131 pcf8574_attach(device_t dev)
  132 {
  133         struct pcf8574_softc *sc;
  134 
  135         sc = device_get_softc(dev);
  136         sc->dev = dev;
  137         sc->addr = iicbus_get_addr(dev);
  138 
  139         /* Treat everything as input because there is no way to tell. */
  140         sc->output_mask = 0;
  141         sc->output_state = 0xff;
  142 
  143         /* Put the device to a safe, known state. */
  144         (void)pcf8574_write(sc, 0xff);
  145 
  146         sx_init(&sc->lock, "pcf8574");
  147         sc->busdev = gpiobus_attach_bus(dev);
  148         if (sc->busdev == NULL) {
  149                 device_printf(dev, "Could not create busdev child\n");
  150                 sx_destroy(&sc->lock);
  151                 return (ENXIO);
  152         }
  153         return (0);
  154 }
  155 
  156 static int
  157 pcf8574_detach(device_t dev)
  158 {
  159         struct pcf8574_softc *sc;
  160 
  161         sc = device_get_softc(dev);
  162 
  163         if (sc->busdev != NULL)
  164                 gpiobus_detach_bus(sc->busdev);
  165 
  166         sx_destroy(&sc->lock);
  167         return (0);
  168 }
  169 
  170 static device_t
  171 pcf8574_get_bus(device_t dev)
  172 {
  173         struct pcf8574_softc *sc;
  174 
  175         sc = device_get_softc(dev);
  176         return (sc->busdev);
  177 }
  178 
  179 static int
  180 pcf8574_pin_max(device_t dev __unused, int *maxpin)
  181 {
  182         *maxpin = NUM_PINS - 1;
  183         return (0);
  184 }
  185 
  186 static int
  187 pcf8574_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
  188 {
  189 
  190         if (pin >= NUM_PINS)
  191                 return (EINVAL);
  192         *caps = PIN_CAPS;
  193         return (0);
  194 }
  195 
  196 static int
  197 pcf8574_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags)
  198 {
  199         struct pcf8574_softc *sc;
  200         uint8_t val, stale;
  201         int error;
  202 
  203         sc = device_get_softc(dev);
  204 
  205         if (pin >= NUM_PINS)
  206                 return (EINVAL);
  207 
  208         sx_xlock(&sc->lock);
  209         error = pcf8574_read(sc, &val);
  210         if (error != 0) {
  211                 dbg_dev_printf(dev, "failed to read from device: %d\n",
  212                     error);
  213                 sx_xunlock(&sc->lock);
  214                 return (error);
  215         }
  216 
  217         /*
  218          * Check for pins whose read value is one, but they are configured
  219          * as outputs with low signal.  This is an impossible combination,
  220          * so change their view to be inputs.
  221          */
  222         stale = val & sc->output_mask & ~sc->output_state;
  223         sc->output_mask &= ~stale;
  224         sc->output_state |= stale;
  225 
  226         if ((sc->output_mask & (1 << pin)) != 0)
  227                 *pflags = GPIO_PIN_OUTPUT;
  228         else
  229                 *pflags = GPIO_PIN_INPUT;
  230         sx_xunlock(&sc->lock);
  231 
  232         return (0);
  233 }
  234 
  235 static int
  236 pcf8574_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
  237 {
  238         struct pcf8574_softc *sc;
  239         int error;
  240         uint8_t val;
  241         bool update_needed;
  242 
  243         sc = device_get_softc(dev);
  244 
  245         if (pin >= NUM_PINS)
  246                 return (EINVAL);
  247         if ((flags & ~PIN_CAPS) != 0)
  248                 return (EINVAL);
  249 
  250         sx_xlock(&sc->lock);
  251         if ((flags & GPIO_PIN_OUTPUT) != 0) {
  252                 sc->output_mask |= 1 << pin;
  253                 update_needed = false;
  254         } else if ((flags & GPIO_PIN_INPUT) != 0) {
  255                 sc->output_mask &= ~(1 << pin);
  256                 sc->output_state |= 1 << pin;
  257                 update_needed = true;
  258         } else {
  259                 KASSERT(false, ("both input and output modes requested"));
  260                 update_needed = false;
  261         }
  262 
  263         if (update_needed) {
  264                 val = sc->output_state | ~sc->output_mask;
  265                 error = pcf8574_write(sc, val);
  266                 if (error != 0)
  267                         dbg_dev_printf(dev, "failed to write to device: %d\n",
  268                             error);
  269         }
  270         sx_xunlock(&sc->lock);
  271 
  272         return (0);
  273 }
  274 
  275 static int
  276 pcf8574_pin_getname(device_t dev, uint32_t pin, char *name)
  277 {
  278 
  279         if (pin >= NUM_PINS)
  280                 return (EINVAL);
  281         snprintf(name, GPIOMAXNAME, "P%d", pin);
  282         return (0);
  283 }
  284 
  285 static int
  286 pcf8574_pin_get(device_t dev, uint32_t pin, unsigned int *on)
  287 {
  288         struct pcf8574_softc *sc;
  289         uint8_t val;
  290         int error;
  291 
  292         sc = device_get_softc(dev);
  293 
  294         sx_xlock(&sc->lock);
  295         if ((sc->output_mask & (1 << pin)) != 0) {
  296                 *on = (sc->output_state & (1 << pin)) != 0;
  297                 sx_xunlock(&sc->lock);
  298                 return (0);
  299         }
  300 
  301         error = pcf8574_read(sc, &val);
  302         if (error != 0) {
  303                 dbg_dev_printf(dev, "failed to read from device: %d\n", error);
  304                 sx_xunlock(&sc->lock);
  305                 return (error);
  306         }
  307         sx_xunlock(&sc->lock);
  308 
  309         *on = (val & (1 << pin)) != 0;
  310         return (0);
  311 }
  312 
  313 static int
  314 pcf8574_pin_set(device_t dev, uint32_t pin, unsigned int on)
  315 {
  316         struct pcf8574_softc *sc;
  317         uint8_t val;
  318         int error;
  319 
  320         sc = device_get_softc(dev);
  321 
  322         if (pin >= NUM_PINS)
  323                 return (EINVAL);
  324 
  325         sx_xlock(&sc->lock);
  326 
  327         if ((sc->output_mask & (1 << pin)) == 0) {
  328                 sx_xunlock(&sc->lock);
  329                 return (EINVAL);
  330         }
  331 
  332         /*
  333          * Algorithm:
  334          * - set all outputs to their recorded state;
  335          * - set all inputs to the high state;
  336          * - apply the requested change.
  337          */
  338         val = sc->output_state | ~sc->output_mask;
  339         val &= ~(1 << pin);
  340         val |= (on != 0) << pin;
  341 
  342         error = pcf8574_write(sc, val);
  343         if (error != 0) {
  344                 dbg_dev_printf(dev, "failed to write to device: %d\n", error);
  345                 sx_xunlock(&sc->lock);
  346                 return (error);
  347         }
  348 
  349         /*
  350          * NB: we can record anything as "output" state of input pins.
  351          * By convention and for convenience it will be recorded as 1.
  352          */
  353         sc->output_state = val;
  354         sx_xunlock(&sc->lock);
  355         return (0);
  356 }
  357 
  358 static int
  359 pcf8574_pin_toggle(device_t dev, uint32_t pin)
  360 {
  361         struct pcf8574_softc *sc;
  362         uint8_t val;
  363         int error;
  364 
  365         sc = device_get_softc(dev);
  366 
  367         if (pin >= NUM_PINS)
  368                 return (EINVAL);
  369 
  370         sx_xlock(&sc->lock);
  371 
  372         if ((sc->output_mask & (1 << pin)) == 0) {
  373                 sx_xunlock(&sc->lock);
  374                 return (EINVAL);
  375         }
  376 
  377         val = sc->output_state | ~sc->output_mask;
  378         val ^= 1 << pin;
  379 
  380         error = pcf8574_write(sc, val);
  381         if (error != 0) {
  382                 dbg_dev_printf(dev, "failed to write to device: %d\n", error);
  383                 sx_xunlock(&sc->lock);
  384                 return (error);
  385         }
  386 
  387         sc->output_state = val;
  388         sx_xunlock(&sc->lock);
  389         return (0);
  390 }
  391 
  392 static device_method_t pcf8574_methods[] = {
  393         DEVMETHOD(device_probe,         pcf8574_probe),
  394         DEVMETHOD(device_attach,        pcf8574_attach),
  395         DEVMETHOD(device_detach,        pcf8574_detach),
  396 
  397         /* GPIO methods */
  398         DEVMETHOD(gpio_get_bus,         pcf8574_get_bus),
  399         DEVMETHOD(gpio_pin_max,         pcf8574_pin_max),
  400         DEVMETHOD(gpio_pin_getcaps,     pcf8574_pin_getcaps),
  401         DEVMETHOD(gpio_pin_getflags,    pcf8574_pin_getflags),
  402         DEVMETHOD(gpio_pin_setflags,    pcf8574_pin_setflags),
  403         DEVMETHOD(gpio_pin_getname,     pcf8574_pin_getname),
  404         DEVMETHOD(gpio_pin_get,         pcf8574_pin_get),
  405         DEVMETHOD(gpio_pin_set,         pcf8574_pin_set),
  406         DEVMETHOD(gpio_pin_toggle,      pcf8574_pin_toggle),
  407 
  408         DEVMETHOD_END
  409 };
  410 
  411 static driver_t pcf8574_driver = {
  412         "gpio",
  413         pcf8574_methods,
  414         sizeof(struct pcf8574_softc)
  415 };
  416 
  417 DRIVER_MODULE(pcf8574, iicbus, pcf8574_driver, 0, 0);
  418 MODULE_DEPEND(pcf8574, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
  419 MODULE_DEPEND(pcf8574, gpiobus, 1, 1, 1);
  420 MODULE_VERSION(pcf8574, 1);
  421 #ifdef FDT
  422 IICBUS_FDT_PNP_INFO(compat_data);
  423 #endif

Cache object: 9d81d51bcb18bfcb7a5269a59ac77e5f


[ 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.