From owner-svn-src-all@freebsd.org Sun Dec 1 23:05:21 2019 Return-Path: Delivered-To: svn-src-all@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 633111BC1BE; Sun, 1 Dec 2019 23:05:21 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 47R3k92VZ7z4N4x; Sun, 1 Dec 2019 23:05:21 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 3585B1E004; Sun, 1 Dec 2019 23:05:21 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id xB1N5LBU007064; Sun, 1 Dec 2019 23:05:21 GMT (envelope-from ian@FreeBSD.org) Received: (from ian@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id xB1N5KQq007061; Sun, 1 Dec 2019 23:05:20 GMT (envelope-from ian@FreeBSD.org) Message-Id: <201912012305.xB1N5KQq007061@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: ian set sender to ian@FreeBSD.org using -f From: Ian Lepore Date: Sun, 1 Dec 2019 23:05:20 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r355276 - in head: share/man/man4 sys/dev/gpio X-SVN-Group: head X-SVN-Commit-Author: ian X-SVN-Commit-Paths: in head: share/man/man4 sys/dev/gpio X-SVN-Commit-Revision: 355276 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 01 Dec 2019 23:05:21 -0000 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@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 - * 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 * * 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 #include -#ifdef FDT -#include -#include -#endif - #include #include -#include #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 +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);