svn commit: r355276 - in head: share/man/man4 sys/dev/gpio
Ian Lepore
ian at FreeBSD.org
Sun Dec 1 23:05:21 UTC 2019
Author: ian
Date: Sun Dec 1 23:05:20 2019
New Revision: 355276
URL: https://svnweb.freebsd.org/changeset/base/355276
Log:
Rewrite gpioiic(4) to use the gpio_pin_* API, and to conform to the modern
FDT bindings document for gpio-i2c devices.
Using the gpio_pin_* functions to acquire/release/manipulate gpio pins
removes the constraint that both gpio pins must belong to the same gpio
controller/bank, and that the gpioiic instance must be a child of gpiobus.
Removing those constraints allows the driver to be fully compatible with
the modern dts bindings for a gpio bitbanged i2c bus.
For hinted attachment, the two gpio pins still must be on the same gpiobus,
and the device instance must be a child of that bus. This preserves
compatibility for existing installations that have use gpioiic(4) with hints.
Modified:
head/share/man/man4/gpioiic.4
head/sys/dev/gpio/gpioiic.c
Modified: head/share/man/man4/gpioiic.4
==============================================================================
--- head/share/man/man4/gpioiic.4 Sun Dec 1 21:29:34 2019 (r355275)
+++ head/share/man/man4/gpioiic.4 Sun Dec 1 23:05:20 2019 (r355276)
@@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
-.Dd May 14, 2014
+.Dd December 1, 2019
.Dt GPIOIIC 4
.Os
.Sh NAME
@@ -37,38 +37,42 @@ kernel configuration file:
.Bd -ragged -offset indent
.Cd "device gpio"
.Cd "device gpioiic"
-.Cd "device iic"
.Cd "device iicbb"
.Cd "device iicbus"
.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+gpioiic_load="YES"
+.Ed
.Sh DESCRIPTION
The
.Nm
driver provides an IIC bit-banging interface using two GPIO pins for the
-SCL and SDA on the
-.Nm gpiobus .
+SCL and SDA lines on the bus.
+.Pp
.Nm
-implements an open collector kind of output, as recommended by the standard,
-when driving the pins on the
-.Nm gpiobus ,
-i.e, they are never switched to the logical value of '1',
-or they are '0' or simply open (Hi-Z/tri-state).
-So the pullup resistors are required so
-.Nm
-can work.
+simulates an open collector kind of output when managing the pins on the
+bus, even on systems which don't directly support configuring gpio pins
+in that mode.
+The pins are never driven to the logical value of '1'.
+They are driven to '0' or switched to input mode (Hi-Z/tri-state), and
+an external pullup resistor pulls the line to the 1 state unless some
+other device on the bus is driving it to 0.
.Pp
+.Sh HINTS CONFIGURATION
On a
.Xr device.hints 5
-based system, like
-.Li MIPS ,
-these values are configurable for the
+based system, such as MIPS, these values are configurable for
.Nm :
.Bl -tag -width ".Va hint.gpioiic.%d.atXXX"
.It Va hint.gpioiic.%d.at
The
.Nm gpiobus
you are attaching to.
-Normally just gpiobus0.
+Normally just gpiobus0 on systems with a single bank of gpio pins.
.It Va hint.gpioiic.%d.pins
This is a bitmask of the pins on the
.Nm gpiobus
@@ -78,6 +82,9 @@ To configure pin 0 and 7, use the bitmask of
0b10000001 and convert it to a hexadecimal value of 0x0081.
Please note that this mask should only ever have two bits set
(any other bits - i.e., pins - will be ignored).
+Because
+.Nm
+must be a child of the gpiobus, both gpio pins must be part of that bus.
.It Va hint.gpioiic.%d.scl
Indicates which bit in the
.Va hint.gpioiic.%d.pins
@@ -91,35 +98,32 @@ should be used as the SDATA
source.
Optional, defaults to 1.
.El
-.Pp
-On a
+.Sh FDT CONFIGURATION
+On an
.Xr FDT 4
-based system, like
-.Li ARM ,
-the DTS part for a
+based system, such as ARM, the DTS node for
.Nm gpioiic
-device usually looks like:
+conforms to the standard bindings document i2c/i2c-gpio.yaml.
+The device node typically appears at the root of the device tree.
+The following is an example of a
+.Nm
+node with one slave device
+on the IIC bus:
.Bd -literal
-gpio: gpio {
-
- gpio-controller;
- ...
-
+/ {
gpioiic0 {
- compatible = "gpioiic";
- /*
- * Attach to GPIO pins 21 and 22. Set them
- * initially as inputs.
- */
- gpios = <&gpio 21 1 0
- &gpio 22 1 0>;
- scl = <0>; /* GPIO pin 21 - optional */
- sda = <1>; /* GPIO pin 22 - optional */
+ compatible = "i2c-gpio";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_gpioiic0>;
+ scl-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
+ sda-gpios = <&gpio7 11 GPIO_ACTIVE_HIGH>;
+ status = "okay";
- /* This is an example of a gpioiic child. */
- gpioiic-child0 {
- compatible = "lm75";
- i2c-address = <0x4f>;
+ /* One slave device on the i2c bus. */
+ rtc at 51 {
+ compatible="nxp,pcf2127";
+ reg = <0x51>;
+ status = "okay";
};
};
};
@@ -128,35 +132,19 @@ gpio: gpio {
Where:
.Bl -tag -width ".Va compatible"
.It Va compatible
-Should always be set to "gpioiic".
-.It Va gpios
-The
-.Va gpios
-property indicates which GPIO pins should be used for SCLOCK and SDATA
-on the GPIO IIC bit-banging bus.
-For more details about the
-.Va gpios
-property, please consult
-.Pa /usr/src/sys/dts/bindings-gpio.txt .
-.It Va scl
-The
-.Va scl
-option indicates which bit in the
-.Va gpios
-should be used as the SCLOCK source.
-Optional, defaults to 0.
-.It Va sda
-The
-.Va sda
-option indicates which bit in the
-.Va gpios
-should be used as the SDATA source.
-Optional, defaults to 1.
+Should be set to "i2c-gpio".
+The deprecated string "gpioiic" is also accepted for backwards compatibility.
+.It Va scl-gpios Va sda-gpios
+These properties indicate which GPIO pins should be used for clock
+and data on the GPIO IIC bit-banging bus.
+There is no requirement that the two pins belong to the same gpio controller.
+.It Va pinctrl-names pinctrl-0
+These properties may be required to configure the chosen pins as gpio
+pins, unless the pins default to that state on your system.
.El
.Sh SEE ALSO
.Xr fdt 4 ,
.Xr gpio 4 ,
-.Xr gpioled 4 ,
.Xr iic 4 ,
.Xr iicbb 4 ,
.Xr iicbus 4
Modified: head/sys/dev/gpio/gpioiic.c
==============================================================================
--- head/sys/dev/gpio/gpioiic.c Sun Dec 1 21:29:34 2019 (r355275)
+++ head/sys/dev/gpio/gpioiic.c Sun Dec 1 23:05:20 2019 (r355276)
@@ -1,9 +1,9 @@
/*-
- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ * SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2009 Oleksandr Tymoshenko <gonzo at freebsd.org>
- * Copyright (c) 2010 Luiz Otavio O Souza
- * All rights reserved.
+ * Copyright (c) 2010 Luiz Otavio O Souza All rights reserved.
+ * Copyright (c) 2019 Ian Lepore <ian at freebsd.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -39,14 +39,8 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/module.h>
-#ifdef FDT
-#include <dev/fdt/fdt_common.h>
-#include <dev/ofw/ofw_bus.h>
-#endif
-
#include <dev/gpio/gpiobusvar.h>
#include <dev/iicbus/iiconf.h>
-#include <dev/iicbus/iicbus.h>
#include "gpiobus_if.h"
#include "iicbb_if.h"
@@ -57,200 +51,281 @@ __FBSDID("$FreeBSD$");
struct gpioiic_softc
{
- device_t sc_dev;
- device_t sc_busdev;
- int scl_pin;
- int sda_pin;
+ device_t dev;
+ gpio_pin_t sclpin;
+ gpio_pin_t sdapin;
};
-static int gpioiic_probe(device_t);
-static int gpioiic_attach(device_t);
+#ifdef FDT
-/* iicbb interface */
-static void gpioiic_reset_bus(device_t);
-static void gpioiic_setsda(device_t, int);
-static void gpioiic_setscl(device_t, int);
-static int gpioiic_getsda(device_t);
-static int gpioiic_getscl(device_t);
-static int gpioiic_reset(device_t, u_char, u_char, u_char *);
+#include <dev/ofw/ofw_bus.h>
+static struct ofw_compat_data compat_data[] = {
+ {"i2c-gpio", true}, /* Standard devicetree compat string */
+ {"gpioiic", true}, /* Deprecated old freebsd compat string */
+ {NULL, false}
+};
+OFWBUS_PNP_INFO(compat_data);
+SIMPLEBUS_PNP_INFO(compat_data);
+
+static phandle_t
+gpioiic_get_node(device_t bus, device_t dev)
+{
+
+ /* Share our fdt node with iicbus so it can find its child nodes. */
+ return (ofw_bus_get_node(bus));
+}
+
static int
-gpioiic_probe(device_t dev)
+gpioiic_setup_fdt_pins(struct gpioiic_softc *sc)
{
- struct gpiobus_ivar *devi;
+ phandle_t node;
+ int err;
-#ifdef FDT
- if (!ofw_bus_status_okay(dev))
- return (ENXIO);
- if (!ofw_bus_is_compatible(dev, "gpioiic"))
- return (ENXIO);
-#endif
- devi = GPIOBUS_IVAR(dev);
- if (devi->npins < GPIOIIC_MIN_PINS) {
- device_printf(dev,
- "gpioiic needs at least %d GPIO pins (only %d given).\n",
- GPIOIIC_MIN_PINS, devi->npins);
- return (ENXIO);
- }
- device_set_desc(dev, "GPIO I2C bit-banging driver");
+ node = ofw_bus_get_node(sc->dev);
- return (BUS_PROBE_DEFAULT);
+ /*
+ * Historically, we used the first two array elements of the gpios
+ * property. The modern bindings specify separate scl-gpios and
+ * sda-gpios properties. We cope with whichever is present.
+ */
+ if (OF_hasprop(node, "gpios")) {
+ if ((err = gpio_pin_get_by_ofw_idx(sc->dev, node,
+ GPIOIIC_SCL_DFLT, &sc->sclpin)) != 0) {
+ device_printf(sc->dev, "invalid gpios property\n");
+ return (err);
+ }
+ if ((err = gpio_pin_get_by_ofw_idx(sc->dev, node,
+ GPIOIIC_SDA_DFLT, &sc->sdapin)) != 0) {
+ device_printf(sc->dev, "ivalid gpios property\n");
+ return (err);
+ }
+ } else {
+ if ((err = gpio_pin_get_by_ofw_property(sc->dev, node,
+ "scl-gpios", &sc->sclpin)) != 0) {
+ device_printf(sc->dev, "missing scl-gpios property\n");
+ return (err);
+ }
+ if ((err = gpio_pin_get_by_ofw_property(sc->dev, node,
+ "sda-gpios", &sc->sdapin)) != 0) {
+ device_printf(sc->dev, "missing sda-gpios property\n");
+ return (err);
+ }
+ }
+ return (0);
}
+#endif /* FDT */
static int
-gpioiic_attach(device_t dev)
+gpioiic_setup_hinted_pins(struct gpioiic_softc *sc)
{
- device_t bitbang;
-#ifdef FDT
- phandle_t node;
- pcell_t pin;
-#endif
- struct gpiobus_ivar *devi;
- struct gpioiic_softc *sc;
+ device_t busdev;
+ const char *busname, *devname;
+ int err, numpins, sclnum, sdanum, unit;
- sc = device_get_softc(dev);
- sc->sc_dev = dev;
- sc->sc_busdev = device_get_parent(dev);
- if (resource_int_value(device_get_name(dev),
- device_get_unit(dev), "scl", &sc->scl_pin))
- sc->scl_pin = GPIOIIC_SCL_DFLT;
- if (resource_int_value(device_get_name(dev),
- device_get_unit(dev), "sda", &sc->sda_pin))
- sc->sda_pin = GPIOIIC_SDA_DFLT;
+ devname = device_get_name(sc->dev);
+ unit = device_get_unit(sc->dev);
+ busdev = device_get_parent(sc->dev);
+ /*
+ * If there is not an "at" hint naming our actual parent, then we
+ * weren't instantiated as a child of gpiobus via hints, and we thus
+ * can't access ivars that only exist for such children.
+ */
+ if (resource_string_value(devname, unit, "at", &busname) != 0 ||
+ (strcmp(busname, device_get_nameunit(busdev)) != 0 &&
+ strcmp(busname, device_get_name(busdev)) != 0)) {
+ return (ENOENT);
+ }
+
+ /* Make sure there were hints for at least two pins. */
+ numpins = gpiobus_get_npins(sc->dev);
+ if (numpins < GPIOIIC_MIN_PINS) {
#ifdef FDT
- if ((node = ofw_bus_get_node(dev)) == -1)
- return (ENXIO);
- if (OF_getencprop(node, "scl", &pin, sizeof(pin)) > 0)
- sc->scl_pin = (int)pin;
- if (OF_getencprop(node, "sda", &pin, sizeof(pin)) > 0)
- sc->sda_pin = (int)pin;
+ /*
+ * Be silent when there are no hints on FDT systems; the FDT
+ * data will provide the pin config (we'll whine if it doesn't).
+ */
+ if (numpins == 0) {
+ return (ENOENT);
+ }
#endif
+ device_printf(sc->dev,
+ "invalid pins hint; it must contain at least %d pins\n",
+ GPIOIIC_MIN_PINS);
+ return (EINVAL);
+ }
- if (sc->scl_pin < 0 || sc->scl_pin > 1)
- sc->scl_pin = GPIOIIC_SCL_DFLT;
- if (sc->sda_pin < 0 || sc->sda_pin > 1)
- sc->sda_pin = GPIOIIC_SDA_DFLT;
+ /*
+ * Our parent bus has already parsed the pins hint and it will use that
+ * info when we call gpio_pin_get_by_child_index(). But we have to
+ * handle the scl/sda index hints that tell us which of the two pins is
+ * the clock and which is the data. They're optional, but if present
+ * they must be a valid index (0 <= index < numpins).
+ */
+ if ((err = resource_int_value(devname, unit, "scl", &sclnum)) != 0)
+ sclnum = GPIOIIC_SCL_DFLT;
+ else if (sclnum < 0 || sclnum >= numpins) {
+ device_printf(sc->dev, "invalid scl hint %d\n", sclnum);
+ return (EINVAL);
+ }
+ if ((err = resource_int_value(devname, unit, "sda", &sdanum)) != 0)
+ sdanum = GPIOIIC_SDA_DFLT;
+ else if (sdanum < 0 || sdanum >= numpins) {
+ device_printf(sc->dev, "invalid sda hint %d\n", sdanum);
+ return (EINVAL);
+ }
- devi = GPIOBUS_IVAR(dev);
- device_printf(dev, "SCL pin: %d, SDA pin: %d\n",
- devi->pins[sc->scl_pin], devi->pins[sc->sda_pin]);
+ /* Allocate gpiobus_pin structs for the pins we found above. */
+ if ((err = gpio_pin_get_by_child_index(sc->dev, sclnum,
+ &sc->sclpin)) != 0)
+ return (err);
+ if ((err = gpio_pin_get_by_child_index(sc->dev, sdanum,
+ &sc->sdapin)) != 0)
+ return (err);
- /* add generic bit-banging code */
- bitbang = device_add_child(dev, "iicbb", -1);
- device_probe_and_attach(bitbang);
-
return (0);
}
-static int
-gpioiic_detach(device_t dev)
-{
-
- bus_generic_detach(dev);
- device_delete_children(dev);
- return (0);
-}
-
-/*
- * Reset bus by setting SDA first and then SCL.
- * Must always be called with gpio bus locked.
- */
static void
-gpioiic_reset_bus(device_t dev)
+gpioiic_setsda(device_t dev, int val)
{
- struct gpioiic_softc *sc = device_get_softc(dev);
+ struct gpioiic_softc *sc = device_get_softc(dev);
+ int err;
- GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, sc->sda_pin,
- GPIO_PIN_INPUT);
- GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, sc->scl_pin,
- GPIO_PIN_INPUT);
+ /*
+ * Some controllers cannot set an output value while a pin is in input
+ * mode; in that case we set the pin again after changing mode.
+ */
+ err = gpio_pin_set_active(sc->sdapin, val);
+ gpio_pin_setflags(sc->sdapin, GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN);
+ if (err != 0)
+ gpio_pin_set_active(sc->sdapin, val);
}
static void
-gpioiic_setpin(struct gpioiic_softc *sc, int pin, int val)
+gpioiic_setscl(device_t dev, int val)
{
- int err;
+ struct gpioiic_softc *sc = device_get_softc(dev);
- if (val == 0) {
- err = GPIOBUS_PIN_SET(sc->sc_busdev, sc->sc_dev, pin, 0);
- GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, pin,
- GPIO_PIN_OUTPUT);
-
- /*
- * Some controllers cannot set output value while a pin is in
- * input mode.
- */
- if (err != 0)
- GPIOBUS_PIN_SET(sc->sc_busdev, sc->sc_dev, pin, 0);
- } else {
- GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, pin,
- GPIO_PIN_INPUT);
- }
+ gpio_pin_setflags(sc->sclpin, GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN);
+ gpio_pin_set_active(sc->sclpin, val);
}
-static void
-gpioiic_setsda(device_t dev, int val)
+static int
+gpioiic_getscl(device_t dev)
{
- struct gpioiic_softc *sc = device_get_softc(dev);
+ struct gpioiic_softc *sc = device_get_softc(dev);
+ bool val;
- gpioiic_setpin(sc, sc->sda_pin, val);
+ gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT);
+ gpio_pin_is_active(sc->sclpin, &val);
+ return (val);
}
-static void
-gpioiic_setscl(device_t dev, int val)
+static int
+gpioiic_getsda(device_t dev)
{
- struct gpioiic_softc *sc = device_get_softc(dev);
+ struct gpioiic_softc *sc = device_get_softc(dev);
+ bool val;
- gpioiic_setpin(sc, sc->scl_pin, val);
+ gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT);
+ gpio_pin_is_active(sc->sdapin, &val);
+ return (val);
}
static int
-gpioiic_getpin(struct gpioiic_softc *sc, int pin)
+gpioiic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
{
- unsigned int val;
+ struct gpioiic_softc *sc = device_get_softc(dev);
- GPIOBUS_PIN_SETFLAGS(sc->sc_busdev, sc->sc_dev, pin, GPIO_PIN_INPUT);
- GPIOBUS_PIN_GET(sc->sc_busdev, sc->sc_dev, pin, &val);
- return ((int)val);
+ /* Stop driving the bus pins. */
+ gpio_pin_setflags(sc->sdapin, GPIO_PIN_INPUT);
+ gpio_pin_setflags(sc->sclpin, GPIO_PIN_INPUT);
+
+ /* Indicate that we have no slave address (master mode). */
+ return (IIC_ENOADDR);
}
-static int
-gpioiic_getscl(device_t dev)
+static void
+gpioiic_cleanup(struct gpioiic_softc *sc)
{
- struct gpioiic_softc *sc = device_get_softc(dev);
- return (gpioiic_getpin(sc, sc->scl_pin));
+ device_delete_children(sc->dev);
+
+ if (sc->sclpin != NULL)
+ gpio_pin_release(sc->sclpin);
+
+ if (sc->sdapin != NULL)
+ gpio_pin_release(sc->sdapin);
}
static int
-gpioiic_getsda(device_t dev)
+gpioiic_probe(device_t dev)
{
- struct gpioiic_softc *sc = device_get_softc(dev);
+ int rv;
- return (gpioiic_getpin(sc, sc->sda_pin));
+ /*
+ * By default we only bid to attach if specifically added by our parent
+ * (usually via hint.gpioiic.#.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, "GPIO I2C");
+
+ return (rv);
}
static int
-gpioiic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
+gpioiic_attach(device_t dev)
{
- struct gpioiic_softc *sc;
+ struct gpioiic_softc *sc = device_get_softc(dev);
+ int err;
- sc = device_get_softc(dev);
- gpioiic_reset_bus(sc->sc_dev);
+ sc->dev = dev;
- return (IIC_ENOADDR);
+ /* Acquire our gpio pins. */
+ err = gpioiic_setup_hinted_pins(sc);
+#ifdef FDT
+ if (err != 0)
+ err = gpioiic_setup_fdt_pins(sc);
+#endif
+ if (err != 0) {
+ device_printf(sc->dev, "no pins configured\n");
+ gpioiic_cleanup(sc);
+ return (ENXIO);
+ }
+
+ /* Say what we came up with for pin config. */
+ device_printf(dev, "SCL pin: %s:%d, SDA pin: %s:%d\n",
+ device_get_nameunit(GPIO_GET_BUS(sc->sclpin->dev)), sc->sclpin->pin,
+ device_get_nameunit(GPIO_GET_BUS(sc->sdapin->dev)), sc->sdapin->pin);
+
+ /* Add the bitbang driver as our only child; it will add iicbus. */
+ device_add_child(sc->dev, "iicbb", -1);
+ return (bus_generic_attach(dev));
}
-#ifdef FDT
-static phandle_t
-gpioiic_get_node(device_t bus, device_t dev)
+static int
+gpioiic_detach(device_t dev)
{
+ struct gpioiic_softc *sc = device_get_softc(dev);
+ int err;
- /* We only have one child, the iicbb, which needs our own node. */
- return (ofw_bus_get_node(bus));
+ if ((err = bus_generic_detach(dev)) != 0)
+ return (err);
+
+ gpioiic_cleanup(sc);
+
+ return (0);
}
-#endif
static devclass_t gpioiic_devclass;
@@ -282,6 +357,7 @@ static driver_t gpioiic_driver = {
};
DRIVER_MODULE(gpioiic, gpiobus, gpioiic_driver, gpioiic_devclass, 0, 0);
+DRIVER_MODULE(gpioiic, simplebus, gpioiic_driver, gpioiic_devclass, 0, 0);
DRIVER_MODULE(iicbb, gpioiic, iicbb_driver, iicbb_devclass, 0, 0);
MODULE_DEPEND(gpioiic, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER);
MODULE_DEPEND(gpioiic, gpiobus, 1, 1, 1);
More information about the svn-src-all
mailing list