diff --git a/sys/dev/bwn/if_bwn_pci.c b/sys/dev/bwn/if_bwn_pci.c index e2ba15dd67c8..a81284158443 100644 --- a/sys/dev/bwn/if_bwn_pci.c +++ b/sys/dev/bwn/if_bwn_pci.c @@ -1,303 +1,292 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_bwn.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include "bhndb_bus_if.h" #include "if_bwn_pcivar.h" /* If non-zero, enable attachment of BWN_QUIRK_UNTESTED devices */ static int attach_untested = 0; TUNABLE_INT("hw.bwn_pci.attach_untested", &attach_untested); /* SIBA Devices */ static const struct bwn_pci_device siba_devices[] = { BWN_BCM_DEV(BCM4306_D11A, "BCM4306 802.11a", BWN_QUIRK_WLAN_DUALCORE|BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4306_D11G, "BCM4306 802.11b/g", BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4306_D11G_ID2, "BCM4306 802.11b/g", BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4306_D11DUAL, "BCM4306 802.11a/b/g", BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4307, "BCM4307 802.11b", 0), BWN_BCM_DEV(BCM4311_D11G, "BCM4311 802.11b/g", 0), BWN_BCM_DEV(BCM4311_D11DUAL, "BCM4311 802.11a/b/g", 0), BWN_BCM_DEV(BCM4311_D11A, "BCM4311 802.11a", BWN_QUIRK_UNTESTED|BWN_QUIRK_WLAN_DUALCORE), BWN_BCM_DEV(BCM4318_D11G, "BCM4318 802.11b/g", 0), BWN_BCM_DEV(BCM4318_D11DUAL, "BCM4318 802.11a/b/g", 0), BWN_BCM_DEV(BCM4318_D11A, "BCM4318 802.11a", BWN_QUIRK_UNTESTED|BWN_QUIRK_WLAN_DUALCORE), BWN_BCM_DEV(BCM4321_D11N, "BCM4321 802.11n Dual-Band", BWN_QUIRK_USBH_UNPOPULATED), BWN_BCM_DEV(BCM4321_D11N2G, "BCM4321 802.11n 2GHz", BWN_QUIRK_USBH_UNPOPULATED), BWN_BCM_DEV(BCM4321_D11N5G, "BCM4321 802.11n 5GHz", BWN_QUIRK_UNTESTED|BWN_QUIRK_USBH_UNPOPULATED), BWN_BCM_DEV(BCM4322_D11N, "BCM4322 802.11n Dual-Band", 0), BWN_BCM_DEV(BCM4322_D11N2G, "BCM4322 802.11n 2GHz", BWN_QUIRK_UNTESTED), BWN_BCM_DEV(BCM4322_D11N5G, "BCM4322 802.11n 5GHz", BWN_QUIRK_UNTESTED), BWN_BCM_DEV(BCM4328_D11G, "BCM4328/4312 802.11g", 0), { 0, 0, NULL, 0 } }; /** BCMA Devices */ static const struct bwn_pci_device bcma_devices[] = { BWN_BCM_DEV(BCM4331_D11N, "BCM4331 802.11n Dual-Band", 0), BWN_BCM_DEV(BCM4331_D11N2G, "BCM4331 802.11n 2GHz", 0), BWN_BCM_DEV(BCM4331_D11N5G, "BCM4331 802.11n 5GHz", 0), BWN_BCM_DEV(BCM43224_D11N, "BCM43224 802.11n Dual-Band", 0), BWN_BCM_DEV(BCM43224_D11N_ID_VEN1, "BCM43224 802.11n Dual-Band",0), BWN_BCM_DEV(BCM43225_D11N2G, "BCM43225 802.11n 2GHz", 0), { 0, 0, NULL, 0} }; /** Device configuration table */ static const struct bwn_pci_devcfg bwn_pci_devcfgs[] = { /* SIBA devices */ { .bridge_hwcfg = &bhndb_pci_siba_generic_hwcfg, .bridge_hwtable = bhndb_pci_generic_hw_table, .bridge_hwprio = bhndb_siba_priority_table, .devices = siba_devices }, /* BCMA devices */ { .bridge_hwcfg = &bhndb_pci_bcma_generic_hwcfg, .bridge_hwtable = bhndb_pci_generic_hw_table, .bridge_hwprio = bhndb_bcma_priority_table, .devices = bcma_devices }, { NULL, NULL, NULL } }; /** Search the device configuration table for an entry matching @p dev. */ static int bwn_pci_find_devcfg(device_t dev, const struct bwn_pci_devcfg **cfg, const struct bwn_pci_device **device) { const struct bwn_pci_devcfg *dvc; const struct bwn_pci_device *dv; for (dvc = bwn_pci_devcfgs; dvc->devices != NULL; dvc++) { for (dv = dvc->devices; dv->device != 0; dv++) { if (pci_get_vendor(dev) == dv->vendor && pci_get_device(dev) == dv->device) { if (cfg != NULL) *cfg = dvc; if (device != NULL) *device = dv; return (0); } } } return (ENOENT); } static int bwn_pci_probe(device_t dev) { const struct bwn_pci_device *ident; if (bwn_pci_find_devcfg(dev, NULL, &ident)) return (ENXIO); /* Skip untested devices */ if (ident->quirks & BWN_QUIRK_UNTESTED && !attach_untested) return (ENXIO); device_set_desc(dev, ident->desc); return (BUS_PROBE_DEFAULT); } static int bwn_pci_attach(device_t dev) { struct bwn_pci_softc *sc; const struct bwn_pci_device *ident; int error; sc = device_get_softc(dev); sc->dev = dev; /* Find our hardware config */ if (bwn_pci_find_devcfg(dev, &sc->devcfg, &ident)) return (ENXIO); /* Save quirk flags */ sc->quirks = ident->quirks; /* Attach bridge device */ if ((error = bhndb_attach_bridge(dev, &sc->bhndb_dev, -1))) return (ENXIO); /* Success */ return (0); } -static int -bwn_pci_detach(device_t dev) -{ - int error; - - if ((error = bus_generic_detach(dev))) - return (error); - - return (device_delete_children(dev)); -} - static void bwn_pci_probe_nomatch(device_t dev, device_t child) { const char *name; name = device_get_name(child); if (name == NULL) name = "unknown device"; device_printf(dev, "<%s> (no driver attached)\n", name); } static const struct bhndb_hwcfg * bwn_pci_get_generic_hwcfg(device_t dev, device_t child) { struct bwn_pci_softc *sc = device_get_softc(dev); return (sc->devcfg->bridge_hwcfg); } static const struct bhndb_hw * bwn_pci_get_bhndb_hwtable(device_t dev, device_t child) { struct bwn_pci_softc *sc = device_get_softc(dev); return (sc->devcfg->bridge_hwtable); } static const struct bhndb_hw_priority * bwn_pci_get_bhndb_hwprio(device_t dev, device_t child) { struct bwn_pci_softc *sc = device_get_softc(dev); return (sc->devcfg->bridge_hwprio); } static bool bwn_pci_is_core_disabled(device_t dev, device_t child, struct bhnd_core_info *core) { struct bwn_pci_softc *sc; sc = device_get_softc(dev); switch (bhnd_core_class(core)) { case BHND_DEVCLASS_WLAN: if (core->unit > 0 && !(sc->quirks & BWN_QUIRK_WLAN_DUALCORE)) return (true); return (false); case BHND_DEVCLASS_ENET: case BHND_DEVCLASS_ENET_MAC: case BHND_DEVCLASS_ENET_PHY: return ((sc->quirks & BWN_QUIRK_ENET_HW_UNPOPULATED) != 0); case BHND_DEVCLASS_USB_HOST: return ((sc->quirks & BWN_QUIRK_USBH_UNPOPULATED) != 0); case BHND_DEVCLASS_SOFTMODEM: return ((sc->quirks & BWN_QUIRK_SOFTMODEM_UNPOPULATED) != 0); default: return (false); } } static device_method_t bwn_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bwn_pci_probe), DEVMETHOD(device_attach, bwn_pci_attach), - DEVMETHOD(device_detach, bwn_pci_detach), + DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_probe_nomatch, bwn_pci_probe_nomatch), /* BHNDB_BUS Interface */ DEVMETHOD(bhndb_bus_get_generic_hwcfg, bwn_pci_get_generic_hwcfg), DEVMETHOD(bhndb_bus_get_hardware_table, bwn_pci_get_bhndb_hwtable), DEVMETHOD(bhndb_bus_get_hardware_prio, bwn_pci_get_bhndb_hwprio), DEVMETHOD(bhndb_bus_is_core_disabled, bwn_pci_is_core_disabled), DEVMETHOD_END }; DEFINE_CLASS_0(bwn_pci, bwn_pci_driver, bwn_pci_methods, sizeof(struct bwn_pci_softc)); DRIVER_MODULE_ORDERED(bwn_pci, pci, bwn_pci_driver, NULL, NULL, SI_ORDER_ANY); MODULE_PNP_INFO("U16:vendor;U16:device;D:#", pci, bwn_siba, siba_devices, nitems(siba_devices) - 1); MODULE_PNP_INFO("U16:vendor;U16:device;D:#", pci, bwn_bcma, bcma_devices, nitems(bcma_devices) - 1); DRIVER_MODULE(bhndb, bwn_pci, bhndb_pci_driver, NULL, NULL); MODULE_DEPEND(bwn_pci, bwn, 1, 1, 1); MODULE_DEPEND(bwn_pci, bhnd, 1, 1, 1); MODULE_DEPEND(bwn_pci, bhndb, 1, 1, 1); MODULE_DEPEND(bwn_pci, bhndb_pci, 1, 1, 1); MODULE_DEPEND(bwn_pci, bcma_bhndb, 1, 1, 1); MODULE_DEPEND(bwn_pci, siba_bhndb, 1, 1, 1); MODULE_VERSION(bwn_pci, 1); diff --git a/sys/dev/iicbus/iicbb.c b/sys/dev/iicbus/iicbb.c index dec5e4bc0c7e..c344bda930b0 100644 --- a/sys/dev/iicbus/iicbb.c +++ b/sys/dev/iicbus/iicbb.c @@ -1,599 +1,588 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998, 2001 Nicolas Souchu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include /* * Generic I2C bit-banging code * * Example: * * iicbus * / \ * iicbb pcf * | \ * bti2c lpbb * * From Linux I2C generic interface * (c) 1998 Gerd Knorr * */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #include #include #include #include "iicbus_if.h" #include "iicbb_if.h" /* Based on the SMBus specification. */ #define DEFAULT_SCL_LOW_TIMEOUT (25 * 1000) struct iicbb_softc { device_t iicbus; u_int udelay; /* signal toggle delay in usec */ u_int io_latency; /* approximate pin toggling latency */ u_int scl_low_timeout; }; static int iicbb_attach(device_t); static void iicbb_child_detached(device_t, device_t); -static int iicbb_detach(device_t); static int iicbb_print_child(device_t, device_t); static int iicbb_probe(device_t); static int iicbb_callback(device_t, int, caddr_t); static int iicbb_start(device_t, u_char, int); static int iicbb_repstart(device_t, u_char, int); static int iicbb_stop(device_t); static int iicbb_write(device_t, const char *, int, int *, int); static int iicbb_read(device_t, char *, int, int *, int, int); static int iicbb_reset(device_t, u_char, u_char, u_char *); static int iicbb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs); static void iicbb_set_speed(struct iicbb_softc *sc, u_char); #ifdef FDT static phandle_t iicbb_get_node(device_t, device_t); #endif static device_method_t iicbb_methods[] = { /* device interface */ DEVMETHOD(device_probe, iicbb_probe), DEVMETHOD(device_attach, iicbb_attach), - DEVMETHOD(device_detach, iicbb_detach), + DEVMETHOD(device_detach, bus_generic_detach), /* bus interface */ DEVMETHOD(bus_child_detached, iicbb_child_detached), DEVMETHOD(bus_print_child, iicbb_print_child), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbb_callback), DEVMETHOD(iicbus_start, iicbb_start), DEVMETHOD(iicbus_repeated_start, iicbb_repstart), DEVMETHOD(iicbus_stop, iicbb_stop), DEVMETHOD(iicbus_write, iicbb_write), DEVMETHOD(iicbus_read, iicbb_read), DEVMETHOD(iicbus_reset, iicbb_reset), DEVMETHOD(iicbus_transfer, iicbb_transfer), #ifdef FDT /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, iicbb_get_node), #endif { 0, 0 } }; driver_t iicbb_driver = { "iicbb", iicbb_methods, sizeof(struct iicbb_softc), }; static int iicbb_probe(device_t dev) { device_set_desc(dev, "I2C bit-banging driver"); return (0); } static int iicbb_attach(device_t dev) { struct iicbb_softc *sc = (struct iicbb_softc *)device_get_softc(dev); sc->iicbus = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY); if (!sc->iicbus) return (ENXIO); sc->scl_low_timeout = DEFAULT_SCL_LOW_TIMEOUT; SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "delay", CTLFLAG_RD, &sc->udelay, 0, "Signal change delay controlled by bus frequency, microseconds"); SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "scl_low_timeout", CTLFLAG_RWTUN, &sc->scl_low_timeout, 0, "SCL low timeout, microseconds"); SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "io_latency", CTLFLAG_RWTUN, &sc->io_latency, 0, "Estimate of pin toggling latency, microseconds"); bus_attach_children(dev); return (0); } -static int -iicbb_detach(device_t dev) -{ - - bus_generic_detach(dev); - device_delete_children(dev); - - return (0); -} - #ifdef FDT static phandle_t iicbb_get_node(device_t bus, device_t dev) { /* We only have one child, the I2C bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } #endif static void iicbb_child_detached( device_t dev, device_t child ) { struct iicbb_softc *sc = (struct iicbb_softc *)device_get_softc(dev); if (child == sc->iicbus) sc->iicbus = NULL; } static int iicbb_print_child(device_t bus, device_t dev) { int error; int retval = 0; u_char oldaddr; retval += bus_print_child_header(bus, dev); /* retrieve the interface I2C address */ error = IICBB_RESET(device_get_parent(bus), IIC_FASTEST, 0, &oldaddr); if (error == IIC_ENOADDR) { retval += printf(" on %s master-only\n", device_get_nameunit(bus)); } else { /* restore the address */ IICBB_RESET(device_get_parent(bus), IIC_FASTEST, oldaddr, NULL); retval += printf(" on %s addr 0x%x\n", device_get_nameunit(bus), oldaddr & 0xff); } return (retval); } #define IICBB_DEBUG #ifdef IICBB_DEBUG static int i2c_debug = 0; SYSCTL_DECL(_hw_i2c); SYSCTL_INT(_hw_i2c, OID_AUTO, iicbb_debug, CTLFLAG_RWTUN, &i2c_debug, 0, "Enable i2c bit-banging driver debug"); #define I2C_DEBUG(x) do { \ if (i2c_debug) (x); \ } while (0) #else #define I2C_DEBUG(x) #endif #define I2C_GETSDA(dev) (IICBB_GETSDA(device_get_parent(dev))) #define I2C_SETSDA(dev, x) (IICBB_SETSDA(device_get_parent(dev), x)) #define I2C_GETSCL(dev) (IICBB_GETSCL(device_get_parent(dev))) #define I2C_SETSCL(dev, x) (IICBB_SETSCL(device_get_parent(dev), x)) static int iicbb_waitforscl(device_t dev) { struct iicbb_softc *sc = device_get_softc(dev); sbintime_t fast_timeout; sbintime_t now, timeout; /* Spin for up to 1 ms, then switch to pause. */ now = sbinuptime(); fast_timeout = now + SBT_1MS; timeout = now + sc->scl_low_timeout * SBT_1US; do { if (I2C_GETSCL(dev)) return (0); cpu_spinwait(); now = sbinuptime(); } while (now < fast_timeout); do { I2C_DEBUG(printf(".")); pause_sbt("iicbb-scl-low", SBT_1MS, 0, C_PREL(2)); if (I2C_GETSCL(dev)) return (0); now = sbinuptime(); } while (now < timeout); I2C_DEBUG(printf("*")); return (IIC_ETIMEOUT); } /* Start the high phase of the clock. */ static int iicbb_clockin(device_t dev, int sda) { /* * Precondition: SCL is low. * Action: * - set SDA to the value; * - release SCL and wait until it's high. * The caller is responsible for keeping SCL high for udelay. * * There should be a data set-up time, 250 ns minimum, between setting * SDA and raising SCL. It's expected that the I/O access latency will * naturally provide that delay. */ I2C_SETSDA(dev, sda); I2C_SETSCL(dev, 1); return (iicbb_waitforscl(dev)); } /* * End the high phase of the clock and wait out the low phase * as nothing interesting happens during it anyway. */ static void iicbb_clockout(device_t dev) { struct iicbb_softc *sc = device_get_softc(dev); /* * Precondition: SCL is high. * Action: * - pull SCL low and hold for udelay. */ I2C_SETSCL(dev, 0); DELAY(sc->udelay); } static int iicbb_sendbit(device_t dev, int bit) { struct iicbb_softc *sc = device_get_softc(dev); int err; err = iicbb_clockin(dev, bit); if (err != 0) return (err); DELAY(sc->udelay); iicbb_clockout(dev); return (0); } /* * Waiting for ACKNOWLEDGE. * * When a chip is being addressed or has received data it will issue an * ACKNOWLEDGE pulse. Therefore the MASTER must release the DATA line * (set it to high level) and then release the CLOCK line. * Now it must wait for the SLAVE to pull the DATA line low. * Actually on the bus this looks like a START condition so nothing happens * because of the fact that the IC's that have not been addressed are doing * nothing. * * When the SLAVE has pulled this line low the MASTER will take the CLOCK * line low and then the SLAVE will release the SDA (data) line. */ static int iicbb_getack(device_t dev) { struct iicbb_softc *sc = device_get_softc(dev); int noack, err; int t; /* Release SDA so that the slave can drive it. */ err = iicbb_clockin(dev, 1); if (err != 0) { I2C_DEBUG(printf("! ")); return (err); } /* Sample SDA until ACK (low) or udelay runs out. */ for (t = 0; t < sc->udelay; t++) { noack = I2C_GETSDA(dev); if (!noack) break; DELAY(1); } DELAY(sc->udelay - t); iicbb_clockout(dev); I2C_DEBUG(printf("%c ", noack ? '-' : '+')); return (noack ? IIC_ENOACK : 0); } static int iicbb_sendbyte(device_t dev, uint8_t data) { int err, i; for (i = 7; i >= 0; i--) { err = iicbb_sendbit(dev, (data & (1 << i)) != 0); if (err != 0) { I2C_DEBUG(printf("w!")); return (err); } } I2C_DEBUG(printf("w%02x", data)); return (0); } static int iicbb_readbyte(device_t dev, bool last, uint8_t *data) { struct iicbb_softc *sc = device_get_softc(dev); int i, err; /* * Release SDA so that the slave can drive it. * We do not use iicbb_clockin() here because we need to release SDA * only once and then we just pulse the SCL. */ *data = 0; I2C_SETSDA(dev, 1); for (i = 7; i >= 0; i--) { I2C_SETSCL(dev, 1); err = iicbb_waitforscl(dev); if (err != 0) { I2C_DEBUG(printf("r! ")); return (err); } DELAY((sc->udelay + 1) / 2); if (I2C_GETSDA(dev)) *data |= 1 << i; DELAY((sc->udelay + 1) / 2); iicbb_clockout(dev); } /* * Send master->slave ACK (low) for more data, * NoACK (high) otherwise. */ iicbb_sendbit(dev, last); I2C_DEBUG(printf("r%02x%c ", *data, last ? '-' : '+')); return (0); } static int iicbb_callback(device_t dev, int index, caddr_t data) { return (IICBB_CALLBACK(device_get_parent(dev), index, data)); } static int iicbb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { iicbb_set_speed(device_get_softc(dev), speed); return (IICBB_RESET(device_get_parent(dev), speed, addr, oldaddr)); } static int iicbb_start_impl(device_t dev, u_char slave, bool repstart) { struct iicbb_softc *sc = device_get_softc(dev); int error; if (!repstart) { I2C_DEBUG(printf("<<")); /* SCL must be high on the idle bus. */ if (iicbb_waitforscl(dev) != 0) { I2C_DEBUG(printf("C!\n")); return (IIC_EBUSERR); } } else { I2C_DEBUG(printf("<")); error = iicbb_clockin(dev, 1); if (error != 0) return (error); /* SDA will go low in the middle of the SCL high phase. */ DELAY((sc->udelay + 1) / 2); } /* * SDA must be high after the earlier stop condition or the end * of Ack/NoAck pulse. */ if (!I2C_GETSDA(dev)) { I2C_DEBUG(printf("D!\n")); return (IIC_EBUSERR); } /* Start: SDA high->low. */ I2C_SETSDA(dev, 0); /* Wait the second half of the SCL high phase. */ DELAY((sc->udelay + 1) / 2); /* Pull SCL low to keep the bus reserved. */ iicbb_clockout(dev); /* send address */ error = iicbb_sendbyte(dev, slave); /* check for ack */ if (error == 0) error = iicbb_getack(dev); if (error != 0) (void)iicbb_stop(dev); return (error); } /* NB: the timeout is ignored. */ static int iicbb_start(device_t dev, u_char slave, int timeout) { return (iicbb_start_impl(dev, slave, false)); } /* NB: the timeout is ignored. */ static int iicbb_repstart(device_t dev, u_char slave, int timeout) { return (iicbb_start_impl(dev, slave, true)); } static int iicbb_stop(device_t dev) { struct iicbb_softc *sc = device_get_softc(dev); int err = 0; /* * Stop: SDA goes from low to high in the middle of the SCL high phase. */ err = iicbb_clockin(dev, 0); if (err != 0) return (err); DELAY((sc->udelay + 1) / 2); I2C_SETSDA(dev, 1); DELAY((sc->udelay + 1) / 2); I2C_DEBUG(printf("%s>>", err != 0 ? "!" : "")); I2C_DEBUG(printf("\n")); return (err); } /* NB: the timeout is ignored. */ static int iicbb_write(device_t dev, const char *buf, int len, int *sent, int timeout) { int bytes, error = 0; bytes = 0; while (len > 0) { /* send byte */ iicbb_sendbyte(dev, (uint8_t)*buf++); /* check for ack */ error = iicbb_getack(dev); if (error != 0) break; bytes++; len--; } *sent = bytes; return (error); } /* NB: whatever delay is, it's ignored. */ static int iicbb_read(device_t dev, char *buf, int len, int *read, int last, int delay) { int bytes = 0; int err = 0; while (len > 0) { err = iicbb_readbyte(dev, (len == 1) ? last : 0, (uint8_t *)buf); if (err != 0) break; buf++; bytes++; len--; } *read = bytes; return (err); } static int iicbb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { int error; error = IICBB_PRE_XFER(device_get_parent(dev)); if (error) return (error); error = iicbus_transfer_gen(dev, msgs, nmsgs); IICBB_POST_XFER(device_get_parent(dev)); return (error); } static void iicbb_set_speed(struct iicbb_softc *sc, u_char speed) { u_int busfreq; int period; /* * udelay is half a period, the clock is held high or low for this long. */ busfreq = IICBUS_GET_FREQUENCY(sc->iicbus, speed); period = 1000000 / 2 / busfreq; /* Hz -> uS */ period -= sc->io_latency; sc->udelay = MAX(period, 1); } DRIVER_MODULE(iicbus, iicbb, iicbus_driver, 0, 0); MODULE_DEPEND(iicbb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(iicbb, IICBB_MODVER); diff --git a/sys/dev/imcsmb/imcsmb.c b/sys/dev/imcsmb/imcsmb.c index 2507d948e8e4..50280ad1b0f7 100644 --- a/sys/dev/imcsmb/imcsmb.c +++ b/sys/dev/imcsmb/imcsmb.c @@ -1,550 +1,525 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Authors: Joe Kloss; Ravi Pokala (rpokala@freebsd.org) * * Copyright (c) 2017-2018 Panasas * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* A detailed description of this device is present in imcsmb_pci.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imcsmb_reg.h" #include "imcsmb_var.h" /* Device methods */ static int imcsmb_attach(device_t dev); -static int imcsmb_detach(device_t dev); static int imcsmb_probe(device_t dev); /* SMBus methods */ static int imcsmb_callback(device_t dev, int index, void *data); static int imcsmb_readb(device_t dev, u_char slave, char cmd, char *byte); static int imcsmb_readw(device_t dev, u_char slave, char cmd, short *word); static int imcsmb_writeb(device_t dev, u_char slave, char cmd, char byte); static int imcsmb_writew(device_t dev, u_char slave, char cmd, short word); /* All the read/write methods wrap around this. */ static int imcsmb_transfer(device_t dev, u_char slave, char cmd, void *data, int word_op, int write_op); /** * device_attach() method. Set up the softc, including getting the set of the * parent imcsmb_pci's registers that we will use. Create the smbus(4) device, * which any SMBus slave device drivers will connect to. * * @author rpokala * * @param[in,out] dev * Device being attached. */ static int imcsmb_attach(device_t dev) { struct imcsmb_softc *sc; int rc; /* Initialize private state */ sc = device_get_softc(dev); sc->dev = dev; sc->imcsmb_pci = device_get_parent(dev); sc->regs = device_get_ivars(dev); /* Create the smbus child */ sc->smbus = device_add_child(dev, "smbus", DEVICE_UNIT_ANY); if (sc->smbus == NULL) { /* Nothing has been allocated, so there's no cleanup. */ device_printf(dev, "Child smbus not added\n"); rc = ENXIO; goto out; } /* Attach the smbus child. */ bus_attach_children(dev); rc = 0; out: return (rc); } -/** - * device_detach() method. attach() didn't do any allocations, so all that's - * needed here is to free up any downstream drivers and children. - * - * @author Joe Kloss - * - * @param[in] dev - * Device being detached. - */ -static int -imcsmb_detach(device_t dev) -{ - int rc; - - /* Detach any attached drivers */ - rc = bus_generic_detach(dev); - if (rc == 0) { - /* Remove all children */ - rc = device_delete_children(dev); - } - - return (rc); -} - /** * device_probe() method. All the actual probing was done by the imcsmb_pci * parent, so just report success. * * @author Joe Kloss * * @param[in,out] dev * Device being probed. */ static int imcsmb_probe(device_t dev) { device_set_desc(dev, "iMC SMBus controller"); return (BUS_PROBE_DEFAULT); } /** * smbus_callback() method. Call the parent imcsmb_pci's request or release * function to quiesce / restart firmware tasks which might use the SMBus. * * @author rpokala * * @param[in] dev * Device being requested or released. * * @param[in] index * Either SMB_REQUEST_BUS or SMB_RELEASE_BUS. * * @param[in] data * Tell's the rest of the SMBus subsystem to allow or disallow waiting; * this driver only works with SMB_DONTWAIT. */ static int imcsmb_callback(device_t dev, int index, void *data) { struct imcsmb_softc *sc; int *how; int rc; sc = device_get_softc(dev); how = (int *) data; switch (index) { case SMB_REQUEST_BUS: { if (*how != SMB_DONTWAIT) { rc = EINVAL; goto out; } rc = imcsmb_pci_request_bus(sc->imcsmb_pci); break; } case SMB_RELEASE_BUS: imcsmb_pci_release_bus(sc->imcsmb_pci); rc = 0; break; default: rc = EINVAL; break; } out: return (rc); } /** * smbus_readb() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[out] byte * The byte which was read. */ static int imcsmb_readb(device_t dev, u_char slave, char cmd, char *byte) { return (imcsmb_transfer(dev, slave, cmd, byte, FALSE, FALSE)); } /** * smbus_readw() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[out] word * The word which was read. */ static int imcsmb_readw(device_t dev, u_char slave, char cmd, short *word) { return (imcsmb_transfer(dev, slave, cmd, word, TRUE, FALSE)); } /** * smbus_writeb() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[in] byte * The byte to write. */ static int imcsmb_writeb(device_t dev, u_char slave, char cmd, char byte) { return (imcsmb_transfer(dev, slave, cmd, &byte, FALSE, TRUE)); } /** * smbus_writew() method. Thin wrapper around imcsmb_transfer(). * * @author Joe Kloss * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[in] word * The word to write. */ static int imcsmb_writew(device_t dev, u_char slave, char cmd, short word) { return (imcsmb_transfer(dev, slave, cmd, &word, TRUE, TRUE)); } /** * Manipulate the PCI control registers to read data from or write data to the * SMBus controller. * * @author Joe Kloss, rpokala * * @param[in] dev * * @param[in] slave * The SMBus address of the target device. * * @param[in] cmd * The SMBus command for the target device; this is the offset for SPDs, * or the register number for TSODs. * * @param[in,out] data * Pointer to either the value to be written, or where to place the value * which was read. * * @param[in] word_op * Bool: is this a word operation? * * @param[in] write_op * Bool: is this a write operation? */ static int imcsmb_transfer(device_t dev, u_char slave, char cmd, void *data, int word_op, int write_op) { struct imcsmb_softc *sc; int i; int rc; uint32_t cmd_val; uint32_t cntl_val; uint32_t orig_cntl_val; uint32_t stat_val; uint16_t *word; uint16_t lword; uint8_t *byte; uint8_t lbyte; sc = device_get_softc(dev); byte = data; word = data; lbyte = *byte; lword = *word; /* We modify the value of the control register; save the original, so * we can restore it later */ orig_cntl_val = pci_read_config(sc->imcsmb_pci, sc->regs->smb_cntl, 4); cntl_val = orig_cntl_val; /* * Set up the SMBCNTL register */ /* [31:28] Clear the existing value of the DTI bits, then set them to * the four high bits of the slave address. */ cntl_val &= ~IMCSMB_CNTL_DTI_MASK; cntl_val |= ((uint32_t) slave & 0xf0) << 24; /* [27:27] Set the CLK_OVERRIDE bit, to enable normal operation */ cntl_val |= IMCSMB_CNTL_CLK_OVERRIDE; /* [26:26] Clear the WRITE_DISABLE bit; the datasheet says this isn't * necessary, but empirically, it is. */ cntl_val &= ~IMCSMB_CNTL_WRITE_DISABLE_BIT; /* [9:9] Clear the POLL_EN bit, to stop the hardware TSOD polling. */ cntl_val &= ~IMCSMB_CNTL_POLL_EN; /* * Set up the SMBCMD register */ /* [31:31] Set the TRIGGER bit; when this gets written, the controller * will issue the command. */ cmd_val = IMCSMB_CMD_TRIGGER_BIT; /* [29:29] For word operations, set the WORD_ACCESS bit. */ if (word_op) { cmd_val |= IMCSMB_CMD_WORD_ACCESS; } /* [27:27] For write operations, set the WRITE bit. */ if (write_op) { cmd_val |= IMCSMB_CMD_WRITE_BIT; } /* [26:24] The three non-DTI, non-R/W bits of the slave address. */ cmd_val |= (uint32_t) ((slave & 0xe) << 23); /* [23:16] The command (offset in the case of an EEPROM, or register in * the case of TSOD or NVDIMM controller). */ cmd_val |= (uint32_t) ((uint8_t) cmd << 16); /* [15:0] The data to be written for a write operation. */ if (write_op) { if (word_op) { /* The datasheet says the controller uses different * endianness for word operations on I2C vs SMBus! * I2C: [15:8] = MSB; [7:0] = LSB * SMB: [15:8] = LSB; [7:0] = MSB * As a practical matter, this controller is very * specifically for use with DIMMs, the SPD (and * NVDIMM controllers) are only accessed as bytes, * the temperature sensor is only accessed as words, and * the temperature sensors are I2C. Thus, byte-swap the * word. */ lword = htobe16(lword); } else { /* For byte operations, the data goes in the LSB, and * the MSB is a don't care. */ lword = (uint16_t) (lbyte & 0xff); } cmd_val |= lword; } /* Write the updated value to the control register first, to disable * the hardware TSOD polling. */ pci_write_config(sc->imcsmb_pci, sc->regs->smb_cntl, cntl_val, 4); /* Poll on the BUSY bit in the status register until clear, or timeout. * We just cleared the auto-poll bit, so we need to make sure the device * is idle before issuing a command. We can safely timeout after 35 ms, * as this is the maximum time the SMBus spec allows for a transaction. */ for (i = 4; i != 0; i--) { stat_val = pci_read_config(sc->imcsmb_pci, sc->regs->smb_stat, 4); if ((stat_val & IMCSMB_STATUS_BUSY_BIT) == 0) { break; } pause("imcsmb", 10 * hz / 1000); } if (i == 0) { device_printf(sc->dev, "transfer: timeout waiting for device to settle\n"); } /* Now that polling has stopped, we can write the command register. This * starts the SMBus command. */ pci_write_config(sc->imcsmb_pci, sc->regs->smb_cmd, cmd_val, 4); /* Wait for WRITE_DATA_DONE/READ_DATA_VALID to be set, or timeout and * fail. We wait up to 35ms. */ for (i = 35000; i != 0; i -= 10) { DELAY(10); stat_val = pci_read_config(sc->imcsmb_pci, sc->regs->smb_stat, 4); /* For a write, the bits holding the data contain the data being * written. You'd think that would cause the READ_DATA_VALID bit * to be cleared, because the data bits no longer contain valid * data from the most recent read operation. While that would be * logical, that's not the case here: READ_DATA_VALID is only * cleared when starting a read operation, and WRITE_DATA_DONE * is only cleared when starting a write operation. */ if (write_op) { if ((stat_val & IMCSMB_STATUS_WRITE_DATA_DONE) != 0) { break; } } else { if ((stat_val & IMCSMB_STATUS_READ_DATA_VALID) != 0) { break; } } } if (i == 0) { rc = SMB_ETIMEOUT; device_printf(dev, "transfer timeout\n"); goto out; } /* It is generally the case that this bit indicates non-ACK, but it * could also indicate other bus errors. There's no way to tell the * difference. */ if ((stat_val & IMCSMB_STATUS_BUS_ERROR_BIT) != 0) { /* While it is not documented, empirically, SPD page-change * commands (writes with DTI = 0x60) always complete with the * error bit set. So, ignore it in those cases. */ if ((slave & 0xf0) != 0x60) { rc = SMB_ENOACK; goto out; } } /* For a read operation, copy the data out */ if (write_op == 0) { if (word_op) { /* The data is returned in bits [15:0]; as discussed * above, byte-swap. */ lword = (uint16_t) (stat_val & 0xffff); lword = htobe16(lword); *word = lword; } else { /* The data is returned in bits [7:0] */ lbyte = (uint8_t) (stat_val & 0xff); *byte = lbyte; } } /* A lack of an error is, de facto, success. */ rc = SMB_ENOERR; out: /* Restore the original value of the control register. */ pci_write_config(sc->imcsmb_pci, sc->regs->smb_cntl, orig_cntl_val, 4); return (rc); } /* Device methods */ static device_method_t imcsmb_methods[] = { /* Device interface */ DEVMETHOD(device_attach, imcsmb_attach), - DEVMETHOD(device_detach, imcsmb_detach), + DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_probe, imcsmb_probe), /* smbus methods */ DEVMETHOD(smbus_callback, imcsmb_callback), DEVMETHOD(smbus_readb, imcsmb_readb), DEVMETHOD(smbus_readw, imcsmb_readw), DEVMETHOD(smbus_writeb, imcsmb_writeb), DEVMETHOD(smbus_writew, imcsmb_writew), DEVMETHOD_END }; static driver_t imcsmb_driver = { .name = "imcsmb", .methods = imcsmb_methods, .size = sizeof(struct imcsmb_softc), }; DRIVER_MODULE(imcsmb, imcsmb_pci, imcsmb_driver, 0, 0); MODULE_DEPEND(imcsmb, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); MODULE_VERSION(imcsmb, 1); DRIVER_MODULE(smbus, imcsmb, smbus_driver, 0, 0); /* vi: set ts=8 sw=4 sts=8 noet: */ diff --git a/sys/dev/imcsmb/imcsmb_pci.c b/sys/dev/imcsmb/imcsmb_pci.c index c6e5d1d690c0..6e9e601989b1 100644 --- a/sys/dev/imcsmb/imcsmb_pci.c +++ b/sys/dev/imcsmb/imcsmb_pci.c @@ -1,337 +1,312 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Authors: Joe Kloss; Ravi Pokala (rpokala@freebsd.org) * * Copyright (c) 2017-2018 Panasas * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imcsmb_reg.h" #include "imcsmb_var.h" /* (Sandy,Ivy)bridge-Xeon and (Has,Broad)well-Xeon CPUs contain one or two * "Integrated Memory Controllers" (iMCs), and each iMC contains two separate * SMBus controllers. These are used for reading SPD data from the DIMMs, and * for reading the "Thermal Sensor on DIMM" (TSODs). The iMC SMBus controllers * are very simple devices, and have limited functionality compared to * full-fledged SMBus controllers, like the one in Intel ICHs and PCHs. * * The publicly available documentation for the iMC SMBus controllers can be * found in the CPU datasheets for (Sandy,Ivy)bridge-Xeon and * (Has,broad)well-Xeon, respectively: * * https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/ * Sandybridge xeon-e5-1600-2600-vol-2-datasheet.pdf * Ivybridge xeon-e5-v2-datasheet-vol-2.pdf * Haswell xeon-e5-v3-datasheet-vol-2.pdf * Broadwell xeon-e5-v4-datasheet-vol-2.pdf * * Another useful resource is the Linux driver. It is not in the main tree. * * https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg840043.html * * The iMC SMBus controllers do not support interrupts (thus, they must be * polled for IO completion). All of the iMC registers are in PCI configuration * space; there is no support for PIO or MMIO. As a result, this driver does * not need to perform and newbus resource manipulation. * * Because there are multiple SMBus controllers sharing the same PCI device, * this driver is actually *two* drivers: * * - "imcsmb" is an smbus(4)-compliant SMBus controller driver * * - "imcsmb_pci" recognizes the PCI device and assigns the appropriate set of * PCI config registers to a specific "imcsmb" instance. */ /* Depending on the motherboard and firmware, the TSODs might be polled by * firmware. Therefore, when this driver accesses these SMBus controllers, the * firmware polling must be disabled as part of requesting the bus, and * re-enabled when releasing the bus. Unfortunately, the details of how to do * this are vendor-specific. Contact your motherboard vendor to get the * information you need to do proper implementations. * * For NVDIMMs which conform to the ACPI "NFIT" standard, the ACPI firmware * manages the NVDIMM; for those which pre-date the standard, the operating * system interacts with the NVDIMM controller using a vendor-proprietary API * over the SMBus. In that case, the NVDIMM driver would be an SMBus slave * device driver, and would interface with the hardware via an SMBus controller * driver such as this one. */ /* PCIe device IDs for (Sandy,Ivy)bridge)-Xeon and (Has,Broad)well-Xeon */ #define PCI_VENDOR_INTEL 0x8086 #define IMCSMB_PCI_DEV_ID_IMC0_SBX 0x3ca8 #define IMCSMB_PCI_DEV_ID_IMC0_IBX 0x0ea8 #define IMCSMB_PCI_DEV_ID_IMC0_HSX 0x2fa8 #define IMCSMB_PCI_DEV_ID_IMC0_BDX 0x6fa8 /* (Sandy,Ivy)bridge-Xeon only have a single memory controller per socket */ #define IMCSMB_PCI_DEV_ID_IMC1_HSX 0x2f68 #define IMCSMB_PCI_DEV_ID_IMC1_BDX 0x6f68 /* There are two SMBus controllers in each device. These define the registers * for each of these devices. */ static struct imcsmb_reg_set imcsmb_regs[] = { { .smb_stat = IMCSMB_REG_STATUS0, .smb_cmd = IMCSMB_REG_COMMAND0, .smb_cntl = IMCSMB_REG_CONTROL0 }, { .smb_stat = IMCSMB_REG_STATUS1, .smb_cmd = IMCSMB_REG_COMMAND1, .smb_cntl = IMCSMB_REG_CONTROL1 }, }; static struct imcsmb_pci_device { uint16_t id; char *name; } imcsmb_pci_devices[] = { {IMCSMB_PCI_DEV_ID_IMC0_SBX, "Intel Sandybridge Xeon iMC 0 SMBus controllers" }, {IMCSMB_PCI_DEV_ID_IMC0_IBX, "Intel Ivybridge Xeon iMC 0 SMBus controllers" }, {IMCSMB_PCI_DEV_ID_IMC0_HSX, "Intel Haswell Xeon iMC 0 SMBus controllers" }, {IMCSMB_PCI_DEV_ID_IMC1_HSX, "Intel Haswell Xeon iMC 1 SMBus controllers" }, {IMCSMB_PCI_DEV_ID_IMC0_BDX, "Intel Broadwell Xeon iMC 0 SMBus controllers" }, {IMCSMB_PCI_DEV_ID_IMC1_BDX, "Intel Broadwell Xeon iMC 1 SMBus controllers" }, {0, NULL}, }; /* Device methods. */ static int imcsmb_pci_attach(device_t dev); -static int imcsmb_pci_detach(device_t dev); static int imcsmb_pci_probe(device_t dev); /** * device_attach() method. Set up the PCI device's softc, then explicitly create * children for the actual imcsmbX controllers. Set up the child's ivars to * point to the proper set of the PCI device's config registers. * * @author Joe Kloss, rpokala * * @param[in,out] dev * Device being attached. */ static int imcsmb_pci_attach(device_t dev) { struct imcsmb_pci_softc *sc; device_t child; int rc; int unit; /* Initialize private state */ sc = device_get_softc(dev); sc->dev = dev; sc->semaphore = 0; /* Create the imcsmbX children */ for (unit = 0; unit < 2; unit++) { child = device_add_child(dev, "imcsmb", DEVICE_UNIT_ANY); if (child == NULL) { /* Nothing has been allocated, so there's no cleanup. */ device_printf(dev, "Child imcsmb not added\n"); rc = ENXIO; goto out; } /* Set the child's ivars to point to the appropriate set of * the PCI device's registers. */ device_set_ivars(child, &imcsmb_regs[unit]); } /* Attach the imcsmbX children. */ bus_attach_children(dev); rc = 0; out: return (rc); } -/** - * device_detach() method. attach() didn't do any allocations, so all that's - * needed here is to free up any downstream drivers and children. - * - * @author Joe Kloss - * - * @param[in] dev - * Device being detached. - */ -static int -imcsmb_pci_detach(device_t dev) -{ - int rc; - - /* Detach any attached drivers */ - rc = bus_generic_detach(dev); - if (rc == 0) { - /* Remove all children */ - rc = device_delete_children(dev); - } - - return (rc); -} - /** * device_probe() method. Look for the right PCI vendor/device IDs. * * @author Joe Kloss, rpokala * * @param[in,out] dev * Device being probed. */ static int imcsmb_pci_probe(device_t dev) { struct imcsmb_pci_device *pci_device; int rc; uint16_t pci_dev_id; rc = ENXIO; if (pci_get_vendor(dev) != PCI_VENDOR_INTEL) { goto out; } pci_dev_id = pci_get_device(dev); for (pci_device = imcsmb_pci_devices; pci_device->name != NULL; pci_device++) { if (pci_dev_id == pci_device->id) { device_set_desc(dev, pci_device->name); rc = BUS_PROBE_DEFAULT; goto out; } } out: return (rc); } /** * Invoked via smbus_callback() -> imcsmb_callback(); clear the semaphore, and * re-enable motherboard-specific DIMM temperature monitoring if needed. This * gets called after the transaction completes. * * @author Joe Kloss * * @param[in,out] dev * The device whose busses to release. */ void imcsmb_pci_release_bus(device_t dev) { struct imcsmb_pci_softc *sc; sc = device_get_softc(dev); /* * IF NEEDED, INSERT MOTHERBOARD-SPECIFIC CODE TO RE-ENABLE DIMM * TEMPERATURE MONITORING HERE. */ atomic_store_rel_int(&sc->semaphore, 0); } /** * Invoked via smbus_callback() -> imcsmb_callback(); set the semaphore, and * disable motherboard-specific DIMM temperature monitoring if needed. This gets * called before the transaction starts. * * @author Joe Kloss * * @param[in,out] dev * The device whose busses to request. */ int imcsmb_pci_request_bus(device_t dev) { struct imcsmb_pci_softc *sc; int rc; sc = device_get_softc(dev); rc = 0; /* We don't want to block. Use a simple test-and-set semaphore to * protect the bus. */ if (atomic_cmpset_acq_int(&sc->semaphore, 0, 1) == 0) { rc = EWOULDBLOCK; } /* * IF NEEDED, INSERT MOTHERBOARD-SPECIFIC CODE TO DISABLE DIMM * TEMPERATURE MONITORING HERE. */ return (rc); } /* Device methods */ static device_method_t imcsmb_pci_methods[] = { /* Device interface */ DEVMETHOD(device_attach, imcsmb_pci_attach), - DEVMETHOD(device_detach, imcsmb_pci_detach), + DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_probe, imcsmb_pci_probe), DEVMETHOD_END }; static driver_t imcsmb_pci_driver = { .name = "imcsmb_pci", .methods = imcsmb_pci_methods, .size = sizeof(struct imcsmb_pci_softc), }; DRIVER_MODULE(imcsmb_pci, pci, imcsmb_pci_driver, 0, 0); MODULE_DEPEND(imcsmb_pci, pci, 1, 1, 1); MODULE_VERSION(imcsmb_pci, 1); /* vi: set ts=8 sw=4 sts=8 noet: */ diff --git a/sys/dev/p2sb/lewisburg_gpiocm.c b/sys/dev/p2sb/lewisburg_gpiocm.c index 7eeab669d625..f5c1792c69e1 100644 --- a/sys/dev/p2sb/lewisburg_gpiocm.c +++ b/sys/dev/p2sb/lewisburg_gpiocm.c @@ -1,352 +1,340 @@ /*- * Copyright (c) 2018 Stormshield * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "lewisburg_gpiocm.h" #include "p2sb.h" #define PADBAR 0x00c #define PADCFG0_GPIORXDIS (1<<9) #define PADCFG0_GPIOTXDIS (1<<8) #define PADCFG0_GPIORXSTATE (1<<1) #define PADCFG0_GPIOTXSTATE (1<<0) #define MAX_PAD_PER_GROUP 24 #define LBGGPIOCM_READ(sc, reg) p2sb_port_read_4(sc->p2sb, sc->port, reg) #define LBGGPIOCM_WRITE(sc, reg, val) \ p2sb_port_write_4(sc->p2sb, sc->port, reg, val) #define LBGGPIOCM_LOCK(sc) p2sb_lock(sc->p2sb) #define LBGGPIOCM_UNLOCK(sc) p2sb_unlock(sc->p2sb) struct lbggroup { int groupid; int npins; int pins_off; device_t dev; char grpname; }; struct lbgcommunity { uint8_t npins; const char *name; uint32_t pad_off; struct lbggroup groups[3]; int ngroups; const char *grpnames; }; #define LBG_COMMUNITY(n, np, g) \ { \ .name = n, \ .npins = np, \ .grpnames = g, \ } static struct lbgcommunity lbg_communities[] = { LBG_COMMUNITY("LewisBurg GPIO Community 0", 72, "ABF"), LBG_COMMUNITY("LewisBurg GPIO Community 1", 61, "CDE"), LBG_COMMUNITY("LewisBurg GPIO Community 2", 0, ""), LBG_COMMUNITY("LewisBurg GPIO Community 3", 12, "I"), LBG_COMMUNITY("LewisBurg GPIO Community 4", 36, "JK"), LBG_COMMUNITY("LewisBurg GPIO Community 5", 66, "GHL"), }; struct lbggpiocm_softc { int port; device_t p2sb; struct lbgcommunity *community; }; static struct lbggroup *lbggpiocm_get_group(struct lbggpiocm_softc *sc, device_t child); static __inline struct lbggroup * lbggpiocm_get_group(struct lbggpiocm_softc *sc, device_t child) { int i; for (i = 0; i < sc->community->ngroups; ++i) if (sc->community->groups[i].dev == child) return (&sc->community->groups[i]); return (NULL); } static __inline uint32_t lbggpiocm_getpad(struct lbggpiocm_softc *sc, uint32_t pin) { if (pin >= sc->community->npins) return (0); return (sc->community->pad_off + 2 * 4 * pin); } int lbggpiocm_get_group_npins(device_t dev, device_t child) { struct lbggpiocm_softc *sc = device_get_softc(dev); struct lbggroup *group; group = lbggpiocm_get_group(sc, child); if (group != NULL) return (group->npins); return (-1); } char lbggpiocm_get_group_name(device_t dev, device_t child) { struct lbggpiocm_softc *sc = device_get_softc(dev); struct lbggroup *group; group = lbggpiocm_get_group(sc, child); if (group != NULL) return (group->grpname); return ('\0'); } static int lbggpiocm_pin2cpin(struct lbggpiocm_softc *sc, device_t child, uint32_t pin) { struct lbggroup *group; group = lbggpiocm_get_group(sc, child); if (group != NULL) return (pin + group->pins_off); return (-1); } int lbggpiocm_pin_setflags(device_t dev, device_t child, uint32_t pin, uint32_t flags) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, padval; int rpin; if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) return (EINVAL); if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == 0) return (EINVAL); rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); padval = LBGGPIOCM_READ(sc, padreg); if (flags & GPIO_PIN_INPUT) { padval &= ~PADCFG0_GPIORXDIS; padval |= PADCFG0_GPIOTXDIS; } else if (flags & GPIO_PIN_OUTPUT) { padval &= ~PADCFG0_GPIOTXDIS; padval |= PADCFG0_GPIORXDIS; } LBGGPIOCM_WRITE(sc, padreg, padval); LBGGPIOCM_UNLOCK(sc); return (0); } int lbggpiocm_pin_get(device_t dev, device_t child, uint32_t pin, uint32_t *value) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, val; int rpin; if (value == NULL) return (EINVAL); rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); val = LBGGPIOCM_READ(sc, padreg); LBGGPIOCM_UNLOCK(sc); if (!(val & PADCFG0_GPIOTXDIS)) *value = !!(val & PADCFG0_GPIOTXSTATE); else *value = !!(val & PADCFG0_GPIORXSTATE); return (0); } int lbggpiocm_pin_set(device_t dev, device_t child, uint32_t pin, uint32_t value) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, padcfg; int rpin; rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); padcfg = LBGGPIOCM_READ(sc, padreg); if (value) padcfg |= PADCFG0_GPIOTXSTATE; else padcfg &= ~PADCFG0_GPIOTXSTATE; LBGGPIOCM_WRITE(sc, padreg, padcfg); LBGGPIOCM_UNLOCK(sc); return (0); } int lbggpiocm_pin_toggle(device_t dev, device_t child, uint32_t pin) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, padcfg; int rpin; rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); padcfg = LBGGPIOCM_READ(sc, padreg); padcfg ^= PADCFG0_GPIOTXSTATE; LBGGPIOCM_WRITE(sc, padreg, padcfg); LBGGPIOCM_UNLOCK(sc); return (0); } static int lbggpiocm_probe(device_t dev) { struct lbggpiocm_softc *sc = device_get_softc(dev); int unit; sc->p2sb = device_get_parent(dev); unit = device_get_unit(dev); KASSERT(unit < nitems(lbg_communities), ("Wrong number of devices or communities")); sc->port = p2sb_get_port(sc->p2sb, unit); sc->community = &lbg_communities[unit]; if (sc->port < 0) return (ENXIO); device_set_desc(dev, sc->community->name); return (BUS_PROBE_DEFAULT); } static int lbggpiocm_attach(device_t dev) { uint32_t npins; struct lbggpiocm_softc *sc; struct lbggroup *group; int i; sc = device_get_softc(dev); if (sc->community->npins == 0) return (ENXIO); LBGGPIOCM_LOCK(sc); sc->community->pad_off = LBGGPIOCM_READ(sc, PADBAR); LBGGPIOCM_UNLOCK(sc); npins = sc->community->npins; for (i = 0; i < nitems(sc->community->groups) && npins > 0; ++i) { group = &sc->community->groups[i]; group->groupid = i; group->grpname = sc->community->grpnames[i]; group->pins_off = i * MAX_PAD_PER_GROUP; group->npins = npins < MAX_PAD_PER_GROUP ? npins : MAX_PAD_PER_GROUP; npins -= group->npins; group->dev = device_add_child(dev, "gpio", -1); } sc->community->ngroups = i; bus_attach_children(dev); return (0); } -static int -lbggpiocm_detach(device_t dev) -{ - int error; - - error = device_delete_children(dev); - if (error) - return (error); - - return (bus_generic_detach(dev)); -} - static device_method_t lbggpiocm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, lbggpiocm_probe), DEVMETHOD(device_attach, lbggpiocm_attach), - DEVMETHOD(device_detach, lbggpiocm_detach), + DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD_END }; static driver_t lbggpiocm_driver = { "lbggpiocm", lbggpiocm_methods, sizeof(struct lbggpiocm_softc) }; DRIVER_MODULE(lbggpiocm, p2sb, lbggpiocm_driver, NULL, NULL); diff --git a/sys/dev/pci/vga_pci.c b/sys/dev/pci/vga_pci.c index 243c0b53dda4..09166c0cbea6 100644 --- a/sys/dev/pci/vga_pci.c +++ b/sys/dev/pci/vga_pci.c @@ -1,770 +1,759 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005 John Baldwin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include /* * Simple driver for PCI VGA display devices. Drivers such as agp(4) and * drm(4) should attach as children of this device. * * XXX: The vgapci name is a hack until we somehow merge the isa vga driver * in or rename it. */ #include #include #include #include #include #include #include #if defined(__amd64__) || defined(__i386__) #include #include #endif #include #include #include /* To re-POST the card. */ struct vga_resource { struct resource *vr_res; int vr_refs; }; struct vga_pci_softc { device_t vga_msi_child; /* Child driver using MSI. */ struct vga_resource vga_bars[PCIR_MAX_BAR_0 + 1]; struct vga_resource vga_bios; }; SYSCTL_DECL(_hw_pci); static struct vga_resource *lookup_res(struct vga_pci_softc *sc, int rid); static struct resource *vga_pci_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); static int vga_pci_release_resource(device_t dev, device_t child, struct resource *r); int vga_pci_default_unit = -1; SYSCTL_INT(_hw_pci, OID_AUTO, default_vgapci_unit, CTLFLAG_RDTUN, &vga_pci_default_unit, -1, "Default VGA-compatible display"); int vga_pci_is_boot_display(device_t dev) { int unit; device_t pcib; uint16_t config; /* Check that the given device is a video card */ if ((pci_get_class(dev) != PCIC_DISPLAY && (pci_get_class(dev) != PCIC_OLD || pci_get_subclass(dev) != PCIS_OLD_VGA))) return (0); unit = device_get_unit(dev); if (vga_pci_default_unit >= 0) { /* * The boot display device was determined by a previous * call to this function, or the user forced it using * the hw.pci.default_vgapci_unit tunable. */ return (vga_pci_default_unit == unit); } /* * The primary video card used as a boot display must have the * "I/O" and "Memory Address Space Decoding" bits set in its * Command register. * * Furthermore, if the card is attached to a bridge, instead of * the root PCI bus, the bridge must have the "VGA Enable" bit * set in its Control register. */ pcib = device_get_parent(device_get_parent(dev)); if (device_get_devclass(device_get_parent(pcib)) == devclass_find("pci")) { /* * The parent bridge is a PCI-to-PCI bridge: check the * value of the "VGA Enable" bit. */ config = pci_read_config(pcib, PCIR_BRIDGECTL_1, 2); if ((config & PCIB_BCR_VGA_ENABLE) == 0) return (0); } config = pci_read_config(dev, PCIR_COMMAND, 2); if ((config & (PCIM_CMD_PORTEN | PCIM_CMD_MEMEN)) == 0) return (0); /* * Disable interrupts until a chipset driver is loaded for * this PCI device. Else unhandled display adapter interrupts * might freeze the CPU. */ pci_write_config(dev, PCIR_COMMAND, config | PCIM_CMD_INTxDIS, 2); /* This video card is the boot display: record its unit number. */ vga_pci_default_unit = unit; device_set_flags(dev, 1); return (1); } static void vga_pci_reset(device_t dev) { int ps; /* * FLR is unsupported on GPUs so attempt a power-management reset by cycling * the device in/out of D3 state. * PCI spec says we can only go into D3 state from D0 state. * Transition from D[12] into D0 before going to D3 state. */ ps = pci_get_powerstate(dev); if (ps != PCI_POWERSTATE_D0 && ps != PCI_POWERSTATE_D3) pci_set_powerstate(dev, PCI_POWERSTATE_D0); if (pci_get_powerstate(dev) != PCI_POWERSTATE_D3) pci_set_powerstate(dev, PCI_POWERSTATE_D3); pci_set_powerstate(dev, ps); } void * vga_pci_map_bios(device_t dev, size_t *size) { struct vga_resource *vr; struct resource *res; device_t pcib; uint32_t rom_addr; uint16_t config; volatile unsigned char *bios; int i, rid, found; #if defined(__amd64__) || defined(__i386__) if (vga_pci_is_boot_display(dev)) { /* * On x86, the System BIOS copy the default display * device's Video BIOS at a fixed location in system * memory (0xC0000, 128 kBytes long) at boot time. * * We use this copy for the default boot device, because * the original ROM may not be valid after boot. */ *size = VGA_PCI_BIOS_SHADOW_SIZE; return (pmap_mapbios(VGA_PCI_BIOS_SHADOW_ADDR, *size)); } #endif pcib = device_get_parent(device_get_parent(dev)); if (device_get_devclass(device_get_parent(pcib)) == devclass_find("pci")) { /* * The parent bridge is a PCI-to-PCI bridge: check the * value of the "VGA Enable" bit. */ config = pci_read_config(pcib, PCIR_BRIDGECTL_1, 2); if ((config & PCIB_BCR_VGA_ENABLE) == 0) { config |= PCIB_BCR_VGA_ENABLE; pci_write_config(pcib, PCIR_BRIDGECTL_1, config, 2); } } switch(pci_read_config(dev, PCIR_HDRTYPE, 1)) { case PCIM_HDRTYPE_BRIDGE: rid = PCIR_BIOS_1; break; case PCIM_HDRTYPE_CARDBUS: rid = 0; break; default: rid = PCIR_BIOS; break; } if (rid == 0) return (NULL); res = vga_pci_alloc_resource(dev, NULL, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (res == NULL) { device_printf(dev, "vga_pci_alloc_resource failed\n"); return (NULL); } bios = rman_get_virtual(res); *size = rman_get_size(res); for (found = i = 0; i < hz; i++) { found = (bios[0] == 0x55 && bios[1] == 0xaa); if (found) break; pause("vgabios", 1); } if (found) return (__DEVOLATILE(void *, bios)); if (bootverbose) device_printf(dev, "initial ROM mapping failed -- resetting\n"); /* * Enable ROM decode */ vga_pci_reset(dev); rom_addr = pci_read_config(dev, rid, 4); rom_addr &= 0x7ff; rom_addr |= rman_get_start(res) | 0x1; pci_write_config(dev, rid, rom_addr, 4); vr = lookup_res(device_get_softc(dev), rid); vga_pci_release_resource(dev, NULL, vr->vr_res); /* * re-allocate */ res = vga_pci_alloc_resource(dev, NULL, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (res == NULL) { device_printf(dev, "vga_pci_alloc_resource failed\n"); return (NULL); } bios = rman_get_virtual(res); *size = rman_get_size(res); for (found = i = 0; i < 3*hz; i++) { found = (bios[0] == 0x55 && bios[1] == 0xaa); if (found) break; pause("vgabios", 1); } if (found) return (__DEVOLATILE(void *, bios)); device_printf(dev, "ROM mapping failed\n"); vr = lookup_res(device_get_softc(dev), rid); vga_pci_release_resource(dev, NULL, vr->vr_res); return (NULL); } void vga_pci_unmap_bios(device_t dev, void *bios) { struct vga_resource *vr; int rid; if (bios == NULL) { return; } #if defined(__amd64__) || defined(__i386__) if (vga_pci_is_boot_display(dev)) { /* We mapped the BIOS shadow copy located at 0xC0000. */ pmap_unmapdev(bios, VGA_PCI_BIOS_SHADOW_SIZE); return; } #endif switch(pci_read_config(dev, PCIR_HDRTYPE, 1)) { case PCIM_HDRTYPE_BRIDGE: rid = PCIR_BIOS_1; break; case PCIM_HDRTYPE_CARDBUS: rid = 0; break; default: rid = PCIR_BIOS; break; } if (rid == 0) return; /* * Look up the PCIR_BIOS resource in our softc. It should match * the address we returned previously. */ vr = lookup_res(device_get_softc(dev), rid); KASSERT(vr->vr_res != NULL, ("vga_pci_unmap_bios: bios not mapped")); KASSERT(rman_get_virtual(vr->vr_res) == bios, ("vga_pci_unmap_bios: mismatch")); vga_pci_release_resource(dev, NULL, vr->vr_res); } int vga_pci_repost(device_t dev) { #if defined(__amd64__) || defined(__i386__) x86regs_t regs; if (!vga_pci_is_boot_display(dev)) return (EINVAL); if (x86bios_get_orm(VGA_PCI_BIOS_SHADOW_ADDR) == NULL) return (ENOTSUP); x86bios_init_regs(®s); regs.R_AH = pci_get_bus(dev); regs.R_AL = (pci_get_slot(dev) << 3) | (pci_get_function(dev) & 0x07); regs.R_DL = 0x80; device_printf(dev, "REPOSTing\n"); x86bios_call(®s, X86BIOS_PHYSTOSEG(VGA_PCI_BIOS_SHADOW_ADDR + 3), X86BIOS_PHYSTOOFF(VGA_PCI_BIOS_SHADOW_ADDR + 3)); x86bios_get_intr(0x10); return (0); #else return (ENOTSUP); #endif } static int vga_pci_probe(device_t dev) { switch (pci_get_class(dev)) { case PCIC_DISPLAY: break; case PCIC_OLD: if (pci_get_subclass(dev) != PCIS_OLD_VGA) return (ENXIO); break; default: return (ENXIO); } /* Probe default display. */ vga_pci_is_boot_display(dev); device_set_desc(dev, "VGA-compatible display"); return (BUS_PROBE_GENERIC); } static int vga_pci_attach(device_t dev) { bus_identify_children(dev); /* Always create a drmn child for now to make it easier on drm. */ device_add_child(dev, "drmn", DEVICE_UNIT_ANY); bus_attach_children(dev); if (vga_pci_is_boot_display(dev)) device_printf(dev, "Boot video device\n"); return (0); } -static int -vga_pci_detach(device_t dev) -{ - int error; - - error = bus_generic_detach(dev); - if (error == 0) - error = device_delete_children(dev); - return (error); -} - /* Bus interface. */ static int vga_pci_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { return (BUS_READ_IVAR(device_get_parent(dev), dev, which, result)); } static int vga_pci_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { return (EINVAL); } static int vga_pci_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { return (BUS_SETUP_INTR(device_get_parent(dev), dev, irq, flags, filter, intr, arg, cookiep)); } static int vga_pci_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { return (BUS_TEARDOWN_INTR(device_get_parent(dev), dev, irq, cookie)); } static struct vga_resource * lookup_res(struct vga_pci_softc *sc, int rid) { int bar; if (rid == PCIR_BIOS) return (&sc->vga_bios); bar = PCI_RID2BAR(rid); if (bar >= 0 && bar <= PCIR_MAX_BAR_0) return (&sc->vga_bars[bar]); return (NULL); } static struct resource * vga_pci_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct vga_resource *vr; switch (type) { case SYS_RES_MEMORY: case SYS_RES_IOPORT: /* * For BARs, we cache the resource so that we only allocate it * from the PCI bus once. */ vr = lookup_res(device_get_softc(dev), *rid); if (vr == NULL) return (NULL); if (vr->vr_res == NULL) vr->vr_res = bus_alloc_resource(dev, type, rid, start, end, count, flags); if (vr->vr_res != NULL) vr->vr_refs++; return (vr->vr_res); } return (bus_alloc_resource(dev, type, rid, start, end, count, flags)); } static int vga_pci_release_resource(device_t dev, device_t child, struct resource *r) { struct vga_resource *vr; int error; switch (rman_get_type(r)) { case SYS_RES_MEMORY: case SYS_RES_IOPORT: /* * For BARs, we release the resource from the PCI bus * when the last child reference goes away. */ vr = lookup_res(device_get_softc(dev), rman_get_rid(r)); if (vr == NULL) return (EINVAL); if (vr->vr_res == NULL) return (EINVAL); KASSERT(vr->vr_res == r, ("vga_pci resource mismatch")); if (vr->vr_refs > 1) { vr->vr_refs--; return (0); } KASSERT(vr->vr_refs > 0, ("vga_pci resource reference count underflow")); error = bus_release_resource(dev, r); if (error == 0) { vr->vr_res = NULL; vr->vr_refs = 0; } return (error); } return (bus_release_resource(dev, r)); } /* PCI interface. */ static uint32_t vga_pci_read_config(device_t dev, device_t child, int reg, int width) { return (pci_read_config(dev, reg, width)); } static void vga_pci_write_config(device_t dev, device_t child, int reg, uint32_t val, int width) { pci_write_config(dev, reg, val, width); } static int vga_pci_enable_busmaster(device_t dev, device_t child) { return (pci_enable_busmaster(dev)); } static int vga_pci_disable_busmaster(device_t dev, device_t child) { return (pci_disable_busmaster(dev)); } static int vga_pci_enable_io(device_t dev, device_t child, int space) { device_printf(dev, "child %s requested pci_enable_io\n", device_get_nameunit(child)); return (pci_enable_io(dev, space)); } static int vga_pci_disable_io(device_t dev, device_t child, int space) { device_printf(dev, "child %s requested pci_disable_io\n", device_get_nameunit(child)); return (pci_disable_io(dev, space)); } static int vga_pci_get_vpd_ident(device_t dev, device_t child, const char **identptr) { return (pci_get_vpd_ident(dev, identptr)); } static int vga_pci_get_vpd_readonly(device_t dev, device_t child, const char *kw, const char **vptr) { return (pci_get_vpd_readonly(dev, kw, vptr)); } static int vga_pci_set_powerstate(device_t dev, device_t child, int state) { device_printf(dev, "child %s requested pci_set_powerstate\n", device_get_nameunit(child)); return (pci_set_powerstate(dev, state)); } static int vga_pci_get_powerstate(device_t dev, device_t child) { device_printf(dev, "child %s requested pci_get_powerstate\n", device_get_nameunit(child)); return (pci_get_powerstate(dev)); } static int vga_pci_assign_interrupt(device_t dev, device_t child) { device_printf(dev, "child %s requested pci_assign_interrupt\n", device_get_nameunit(child)); return (PCI_ASSIGN_INTERRUPT(device_get_parent(dev), dev)); } static int vga_pci_find_cap(device_t dev, device_t child, int capability, int *capreg) { return (pci_find_cap(dev, capability, capreg)); } static int vga_pci_find_next_cap(device_t dev, device_t child, int capability, int start, int *capreg) { return (pci_find_next_cap(dev, capability, start, capreg)); } static int vga_pci_find_extcap(device_t dev, device_t child, int capability, int *capreg) { return (pci_find_extcap(dev, capability, capreg)); } static int vga_pci_find_next_extcap(device_t dev, device_t child, int capability, int start, int *capreg) { return (pci_find_next_extcap(dev, capability, start, capreg)); } static int vga_pci_find_htcap(device_t dev, device_t child, int capability, int *capreg) { return (pci_find_htcap(dev, capability, capreg)); } static int vga_pci_find_next_htcap(device_t dev, device_t child, int capability, int start, int *capreg) { return (pci_find_next_htcap(dev, capability, start, capreg)); } static int vga_pci_alloc_msi(device_t dev, device_t child, int *count) { struct vga_pci_softc *sc; int error; sc = device_get_softc(dev); if (sc->vga_msi_child != NULL) return (EBUSY); error = pci_alloc_msi(dev, count); if (error == 0) sc->vga_msi_child = child; return (error); } static int vga_pci_alloc_msix(device_t dev, device_t child, int *count) { struct vga_pci_softc *sc; int error; sc = device_get_softc(dev); if (sc->vga_msi_child != NULL) return (EBUSY); error = pci_alloc_msix(dev, count); if (error == 0) sc->vga_msi_child = child; return (error); } static int vga_pci_remap_msix(device_t dev, device_t child, int count, const u_int *vectors) { struct vga_pci_softc *sc; sc = device_get_softc(dev); if (sc->vga_msi_child != child) return (ENXIO); return (pci_remap_msix(dev, count, vectors)); } static int vga_pci_release_msi(device_t dev, device_t child) { struct vga_pci_softc *sc; int error; sc = device_get_softc(dev); if (sc->vga_msi_child != child) return (ENXIO); error = pci_release_msi(dev); if (error == 0) sc->vga_msi_child = NULL; return (error); } static int vga_pci_msi_count(device_t dev, device_t child) { return (pci_msi_count(dev)); } static int vga_pci_msix_count(device_t dev, device_t child) { return (pci_msix_count(dev)); } static bus_dma_tag_t vga_pci_get_dma_tag(device_t bus, device_t child) { return (bus_get_dma_tag(bus)); } static device_method_t vga_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, vga_pci_probe), DEVMETHOD(device_attach, vga_pci_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), - DEVMETHOD(device_detach, vga_pci_detach), + DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, vga_pci_read_ivar), DEVMETHOD(bus_write_ivar, vga_pci_write_ivar), DEVMETHOD(bus_setup_intr, vga_pci_setup_intr), DEVMETHOD(bus_teardown_intr, vga_pci_teardown_intr), DEVMETHOD(bus_alloc_resource, vga_pci_alloc_resource), DEVMETHOD(bus_release_resource, vga_pci_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_get_dma_tag, vga_pci_get_dma_tag), /* PCI interface */ DEVMETHOD(pci_read_config, vga_pci_read_config), DEVMETHOD(pci_write_config, vga_pci_write_config), DEVMETHOD(pci_enable_busmaster, vga_pci_enable_busmaster), DEVMETHOD(pci_disable_busmaster, vga_pci_disable_busmaster), DEVMETHOD(pci_enable_io, vga_pci_enable_io), DEVMETHOD(pci_disable_io, vga_pci_disable_io), DEVMETHOD(pci_get_vpd_ident, vga_pci_get_vpd_ident), DEVMETHOD(pci_get_vpd_readonly, vga_pci_get_vpd_readonly), DEVMETHOD(pci_get_powerstate, vga_pci_get_powerstate), DEVMETHOD(pci_set_powerstate, vga_pci_set_powerstate), DEVMETHOD(pci_assign_interrupt, vga_pci_assign_interrupt), DEVMETHOD(pci_find_cap, vga_pci_find_cap), DEVMETHOD(pci_find_next_cap, vga_pci_find_next_cap), DEVMETHOD(pci_find_extcap, vga_pci_find_extcap), DEVMETHOD(pci_find_next_extcap, vga_pci_find_next_extcap), DEVMETHOD(pci_find_htcap, vga_pci_find_htcap), DEVMETHOD(pci_find_next_htcap, vga_pci_find_next_htcap), DEVMETHOD(pci_alloc_msi, vga_pci_alloc_msi), DEVMETHOD(pci_alloc_msix, vga_pci_alloc_msix), DEVMETHOD(pci_remap_msix, vga_pci_remap_msix), DEVMETHOD(pci_release_msi, vga_pci_release_msi), DEVMETHOD(pci_msi_count, vga_pci_msi_count), DEVMETHOD(pci_msix_count, vga_pci_msix_count), { 0, 0 } }; static driver_t vga_pci_driver = { "vgapci", vga_pci_methods, sizeof(struct vga_pci_softc), }; DRIVER_MODULE(vgapci, pci, vga_pci_driver, 0, 0); MODULE_DEPEND(vgapci, x86bios, 1, 1, 1); diff --git a/sys/dev/ppbus/ppbconf.c b/sys/dev/ppbus/ppbconf.c index 17fef1eebde7..2c3f17318747 100644 --- a/sys/dev/ppbus/ppbconf.c +++ b/sys/dev/ppbus/ppbconf.c @@ -1,601 +1,586 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, 1998, 1999 Nicolas Souchu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * */ #include #include "opt_ppb_1284.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ppbus_if.h" #define DEVTOSOFTC(dev) ((struct ppb_data *)device_get_softc(dev)) static MALLOC_DEFINE(M_PPBUSDEV, "ppbusdev", "Parallel Port bus device"); static int ppbus_intr(void *arg); /* * Device methods */ static int ppbus_print_child(device_t bus, device_t dev) { struct ppb_device *ppbdev; int retval; retval = bus_print_child_header(bus, dev); ppbdev = (struct ppb_device *)device_get_ivars(dev); if (ppbdev->flags != 0) retval += printf(" flags 0x%x", ppbdev->flags); retval += bus_print_child_footer(bus, dev); return (retval); } static int ppbus_probe(device_t dev) { device_set_desc(dev, "Parallel port bus"); return (0); } /* * ppbus_add_child() * * Add a ppbus device, allocate/initialize the ivars */ static device_t ppbus_add_child(device_t dev, u_int order, const char *name, int unit) { struct ppb_device *ppbdev; device_t child; /* allocate ivars for the new ppbus child */ ppbdev = malloc(sizeof(struct ppb_device), M_PPBUSDEV, M_NOWAIT | M_ZERO); if (!ppbdev) return (NULL); /* initialize the ivars */ ppbdev->name = name; /* add the device as a child to the ppbus bus with the allocated * ivars */ child = device_add_child_ordered(dev, order, name, unit); device_set_ivars(child, ppbdev); return (child); } static int ppbus_read_ivar(device_t bus, device_t dev, int index, uintptr_t* val) { switch (index) { case PPBUS_IVAR_MODE: /* XXX yet device mode = ppbus mode = chipset mode */ *val = (u_long)ppb_get_mode(bus); break; default: return (ENOENT); } return (0); } static int ppbus_write_ivar(device_t bus, device_t dev, int index, uintptr_t val) { switch (index) { case PPBUS_IVAR_MODE: /* XXX yet device mode = ppbus mode = chipset mode */ ppb_set_mode(bus, val); break; default: return (ENOENT); } return (0); } #define PPB_PNP_PRINTER 0 #define PPB_PNP_MODEM 1 #define PPB_PNP_NET 2 #define PPB_PNP_HDC 3 #define PPB_PNP_PCMCIA 4 #define PPB_PNP_MEDIA 5 #define PPB_PNP_FDC 6 #define PPB_PNP_PORTS 7 #define PPB_PNP_SCANNER 8 #define PPB_PNP_DIGICAM 9 #ifndef DONTPROBE_1284 static char *pnp_tokens[] = { "PRINTER", "MODEM", "NET", "HDC", "PCMCIA", "MEDIA", "FDC", "PORTS", "SCANNER", "DIGICAM", "", NULL }; #if 0 static char *pnp_classes[] = { "printer", "modem", "network device", "hard disk", "PCMCIA", "multimedia device", "floppy disk", "ports", "scanner", "digital camera", "unknown device", NULL }; #endif /* * search_token() * * Search the first occurrence of a token within a string */ static char * search_token(char *str, int slen, char *token) { int tlen, i; #define UNKNOWN_LENGTH -1 if (slen == UNKNOWN_LENGTH) /* get string's length */ slen = strlen(str); /* get token's length */ tlen = strlen(token); if (tlen == 0) return (str); for (i = 0; i <= slen-tlen; i++) { if (strncmp(str + i, token, tlen) == 0) return (&str[i]); } return (NULL); } /* * ppb_pnp_detect() * * Returns the class id. of the peripherial, -1 otherwise */ static int ppb_pnp_detect(device_t bus) { char *token, *class = NULL; int i, len, error; int class_id = -1; char str[PPB_PnP_STRING_SIZE+1]; device_printf(bus, "Probing for PnP devices:\n"); if ((error = ppb_1284_read_id(bus, PPB_NIBBLE, str, PPB_PnP_STRING_SIZE, &len))) goto end_detect; #ifdef DEBUG_1284 device_printf(bus, " %d characters: ", len); for (i = 0; i < len; i++) printf("%c(0x%x) ", str[i], str[i]); printf("\n"); #endif /* replace ';' characters by '\0' */ for (i = 0; i < len; i++) str[i] = (str[i] == ';') ? '\0' : str[i]; if ((token = search_token(str, len, "MFG")) != NULL || (token = search_token(str, len, "MANUFACTURER")) != NULL) device_printf(bus, "<%s", search_token(token, UNKNOWN_LENGTH, ":") + 1); else device_printf(bus, ""); if ((token = search_token(str, len, "CLS")) != NULL) { class = search_token(token, UNKNOWN_LENGTH, ":") + 1; printf(" %s", class); } if ((token = search_token(str, len, "CMD")) != NULL || (token = search_token(str, len, "COMMAND")) != NULL) printf(" %s", search_token(token, UNKNOWN_LENGTH, ":") + 1); printf("\n"); if (class) /* identify class ident */ for (i = 0; pnp_tokens[i] != NULL; i++) { if (search_token(class, len, pnp_tokens[i]) != NULL) { class_id = i; goto end_detect; } } class_id = PPB_PnP_UNKNOWN; end_detect: return (class_id); } /* * ppb_scan_bus() * * Scan the ppbus for IEEE1284 compliant devices */ static int ppb_scan_bus(device_t bus) { struct ppb_data * ppb = (struct ppb_data *)device_get_softc(bus); int error = 0; /* try all IEEE1284 modes, for one device only * * XXX We should implement the IEEE1284.3 standard to detect * daisy chained devices */ error = ppb_1284_negociate(bus, PPB_NIBBLE, PPB_REQUEST_ID); if ((ppb->state == PPB_ERROR) && (ppb->error == PPB_NOT_IEEE1284)) goto end_scan; ppb_1284_terminate(bus); device_printf(bus, "IEEE1284 device found "); if (!(error = ppb_1284_negociate(bus, PPB_NIBBLE, 0))) { printf("/NIBBLE"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_PS2, 0))) { printf("/PS2"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_ECP, 0))) { printf("/ECP"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_ECP, PPB_USE_RLE))) { printf("/ECP_RLE"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_EPP, 0))) { printf("/EPP"); ppb_1284_terminate(bus); } /* try more IEEE1284 modes */ if (bootverbose) { if (!(error = ppb_1284_negociate(bus, PPB_NIBBLE, PPB_REQUEST_ID))) { printf("/NIBBLE_ID"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_PS2, PPB_REQUEST_ID))) { printf("/PS2_ID"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_ECP, PPB_REQUEST_ID))) { printf("/ECP_ID"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_ECP, PPB_REQUEST_ID | PPB_USE_RLE))) { printf("/ECP_RLE_ID"); ppb_1284_terminate(bus); } if (!(error = ppb_1284_negociate(bus, PPB_COMPATIBLE, PPB_EXTENSIBILITY_LINK))) { printf("/Extensibility Link"); ppb_1284_terminate(bus); } } printf("\n"); /* detect PnP devices */ ppb->class_id = ppb_pnp_detect(bus); return (0); end_scan: return (error); } #endif /* !DONTPROBE_1284 */ static int ppbus_attach(device_t dev) { struct ppb_data *ppb = device_get_softc(dev); int error, rid; error = BUS_READ_IVAR(device_get_parent(dev), dev, PPC_IVAR_LOCK, (uintptr_t *)&ppb->ppc_lock); if (error) { device_printf(dev, "Unable to fetch parent's lock\n"); return (error); } rid = 0; ppb->ppc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE); if (ppb->ppc_irq_res != NULL) { mtx_lock(ppb->ppc_lock); error = BUS_WRITE_IVAR(device_get_parent(dev), dev, PPC_IVAR_INTR_HANDLER, (uintptr_t)&ppbus_intr); mtx_unlock(ppb->ppc_lock); if (error) { device_printf(dev, "Unable to set interrupt handler\n"); return (error); } } /* Locate our children */ bus_identify_children(dev); #ifndef DONTPROBE_1284 /* detect IEEE1284 compliant devices */ mtx_lock(ppb->ppc_lock); ppb_scan_bus(dev); mtx_unlock(ppb->ppc_lock); #endif /* !DONTPROBE_1284 */ /* launch attachment of the added children */ bus_attach_children(dev); return (0); } -static int -ppbus_detach(device_t dev) -{ - int error; - - error = bus_generic_detach(dev); - if (error) - return (error); - - /* detach & delete all children */ - device_delete_children(dev); - - return (0); -} - static int ppbus_intr(void *arg) { struct ppb_device *ppbdev; struct ppb_data *ppb = arg; mtx_assert(ppb->ppc_lock, MA_OWNED); if (ppb->ppb_owner == NULL) return (ENOENT); ppbdev = device_get_ivars(ppb->ppb_owner); if (ppbdev->intr_hook == NULL) return (ENOENT); ppbdev->intr_hook(ppbdev->intr_arg); return (0); } static int ppbus_setup_intr(device_t bus, device_t child, struct resource *r, int flags, driver_filter_t *filt, void (*ihand)(void *), void *arg, void **cookiep) { struct ppb_device *ppbdev = device_get_ivars(child); struct ppb_data *ppb = DEVTOSOFTC(bus); /* We do not support filters. */ if (filt != NULL || ihand == NULL) return (EINVAL); /* Can only attach handlers to the parent device's resource. */ if (ppb->ppc_irq_res != r) return (EINVAL); mtx_lock(ppb->ppc_lock); ppbdev->intr_hook = ihand; ppbdev->intr_arg = arg; *cookiep = ppbdev; mtx_unlock(ppb->ppc_lock); return (0); } static int ppbus_teardown_intr(device_t bus, device_t child, struct resource *r, void *ih) { struct ppb_device *ppbdev = device_get_ivars(child); struct ppb_data *ppb = DEVTOSOFTC(bus); mtx_lock(ppb->ppc_lock); if (ppbdev != ih || ppb->ppc_irq_res != r) { mtx_unlock(ppb->ppc_lock); return (EINVAL); } ppbdev->intr_hook = NULL; mtx_unlock(ppb->ppc_lock); return (0); } /* * ppb_request_bus() * * Allocate the device to perform transfers. * * how : PPB_WAIT or PPB_DONTWAIT */ int ppb_request_bus(device_t bus, device_t dev, int how) { struct ppb_data *ppb = DEVTOSOFTC(bus); struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); int error = 0; mtx_assert(ppb->ppc_lock, MA_OWNED); while (!error) { if (ppb->ppb_owner) { switch (how) { case PPB_WAIT | PPB_INTR: error = mtx_sleep(ppb, ppb->ppc_lock, PPBPRI | PCATCH, "ppbreq", 0); break; case PPB_WAIT | PPB_NOINTR: error = mtx_sleep(ppb, ppb->ppc_lock, PPBPRI, "ppbreq", 0); break; default: return (EWOULDBLOCK); } } else { ppb->ppb_owner = dev; /* restore the context of the device * The first time, ctx.valid is certainly false * then do not change anything. This is useful for * drivers that do not set there operating mode * during attachement */ if (ppbdev->ctx.valid) ppb_set_mode(bus, ppbdev->ctx.mode); return (0); } } return (error); } /* * ppb_release_bus() * * Release the device allocated with ppb_request_bus() */ int ppb_release_bus(device_t bus, device_t dev) { struct ppb_data *ppb = DEVTOSOFTC(bus); struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); mtx_assert(ppb->ppc_lock, MA_OWNED); if (ppb->ppb_owner != dev) return (EACCES); /* save the context of the device */ ppbdev->ctx.mode = ppb_get_mode(bus); /* ok, now the context of the device is valid */ ppbdev->ctx.valid = 1; ppb->ppb_owner = 0; /* wakeup waiting processes */ wakeup(ppb); return (0); } static device_method_t ppbus_methods[] = { /* device interface */ DEVMETHOD(device_probe, ppbus_probe), DEVMETHOD(device_attach, ppbus_attach), - DEVMETHOD(device_detach, ppbus_detach), + DEVMETHOD(device_detach, bus_generic_detach), /* bus interface */ DEVMETHOD(bus_add_child, ppbus_add_child), DEVMETHOD(bus_print_child, ppbus_print_child), DEVMETHOD(bus_read_ivar, ppbus_read_ivar), DEVMETHOD(bus_write_ivar, ppbus_write_ivar), DEVMETHOD(bus_setup_intr, ppbus_setup_intr), DEVMETHOD(bus_teardown_intr, ppbus_teardown_intr), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), { 0, 0 } }; static driver_t ppbus_driver = { "ppbus", ppbus_methods, sizeof(struct ppb_data), }; DRIVER_MODULE(ppbus, ppc, ppbus_driver, 0, 0); diff --git a/sys/dev/pwm/pwmbus.c b/sys/dev/pwm/pwmbus.c index ae40a23d0d45..c0c07a36c277 100644 --- a/sys/dev/pwm/pwmbus.c +++ b/sys/dev/pwm/pwmbus.c @@ -1,282 +1,271 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 Emmanuel Vadot * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include "pwmbus_if.h" /* * bus_if methods... */ static device_t pwmbus_add_child(device_t dev, u_int order, const char *name, int unit) { device_t child; struct pwmbus_ivars *ivars; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (child); ivars = malloc(sizeof(struct pwmbus_ivars), M_DEVBUF, M_NOWAIT | M_ZERO); if (ivars == NULL) { device_delete_child(dev, child); return (NULL); } device_set_ivars(child, ivars); return (child); } static int pwmbus_child_location(device_t dev, device_t child, struct sbuf *sb) { struct pwmbus_ivars *ivars; ivars = device_get_ivars(child); sbuf_printf(sb, "hwdev=%s channel=%u", device_get_nameunit(device_get_parent(dev)), ivars->pi_channel); return (0); } static void pwmbus_hinted_child(device_t dev, const char *dname, int dunit) { struct pwmbus_ivars *ivars; device_t child; child = pwmbus_add_child(dev, 0, dname, dunit); /* * If there is a channel hint, use it. Otherwise pi_channel was * initialized to zero, so that's the channel we'll use. */ ivars = device_get_ivars(child); resource_int_value(dname, dunit, "channel", &ivars->pi_channel); } static int pwmbus_print_child(device_t dev, device_t child) { struct pwmbus_ivars *ivars; int rv; ivars = device_get_ivars(child); rv = bus_print_child_header(dev, child); rv += printf(" channel %u", ivars->pi_channel); rv += bus_print_child_footer(dev, child); return (rv); } static void pwmbus_probe_nomatch(device_t dev, device_t child) { struct pwmbus_ivars *ivars; ivars = device_get_ivars(child); if (ivars != NULL) device_printf(dev, " on channel %u\n", ivars->pi_channel); return; } static int pwmbus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct pwmbus_ivars *ivars; ivars = device_get_ivars(child); switch (which) { case PWMBUS_IVAR_CHANNEL: *(u_int *)result = ivars->pi_channel; break; default: return (EINVAL); } return (0); } /* * device_if methods... */ static int pwmbus_probe(device_t dev) { device_set_desc(dev, "PWM bus"); return (BUS_PROBE_GENERIC); } static int pwmbus_attach(device_t dev) { struct pwmbus_softc *sc; struct pwmbus_ivars *ivars; device_t child, parent; u_int chan; sc = device_get_softc(dev); sc->dev = dev; parent = device_get_parent(dev); if (PWMBUS_CHANNEL_COUNT(parent, &sc->nchannels) != 0 || sc->nchannels == 0) { device_printf(sc->dev, "No channels on parent %s\n", device_get_nameunit(parent)); return (ENXIO); } /* Add a pwmc(4) child for each channel. */ for (chan = 0; chan < sc->nchannels; ++chan) { if ((child = pwmbus_add_child(sc->dev, 0, "pwmc", -1)) == NULL) { device_printf(dev, "failed to add pwmc child device " "for channel %u\n", chan); continue; } ivars = device_get_ivars(child); ivars->pi_channel = chan; } bus_enumerate_hinted_children(dev); bus_identify_children(dev); bus_attach_children(dev); return (0); } -static int -pwmbus_detach(device_t dev) -{ - int rv; - - if ((rv = bus_generic_detach(dev)) == 0) - rv = device_delete_children(dev); - - return (rv); -} - /* * pwmbus_if methods... */ static int pwmbus_channel_config(device_t dev, u_int chan, u_int period, u_int duty) { return (PWMBUS_CHANNEL_CONFIG(device_get_parent(dev), chan, period, duty)); } static int pwmbus_channel_get_config(device_t dev, u_int chan, u_int *period, u_int *duty) { return (PWMBUS_CHANNEL_GET_CONFIG(device_get_parent(dev), chan, period, duty)); } static int pwmbus_channel_get_flags(device_t dev, u_int chan, uint32_t *flags) { return (PWMBUS_CHANNEL_GET_FLAGS(device_get_parent(dev), chan, flags)); } static int pwmbus_channel_enable(device_t dev, u_int chan, bool enable) { return (PWMBUS_CHANNEL_ENABLE(device_get_parent(dev), chan, enable)); } static int pwmbus_channel_set_flags(device_t dev, u_int chan, uint32_t flags) { return (PWMBUS_CHANNEL_SET_FLAGS(device_get_parent(dev), chan, flags)); } static int pwmbus_channel_is_enabled(device_t dev, u_int chan, bool *enable) { return (PWMBUS_CHANNEL_IS_ENABLED(device_get_parent(dev), chan, enable)); } static int pwmbus_channel_count(device_t dev, u_int *nchannel) { return (PWMBUS_CHANNEL_COUNT(device_get_parent(dev), nchannel)); } static device_method_t pwmbus_methods[] = { /* device_if */ DEVMETHOD(device_probe, pwmbus_probe), DEVMETHOD(device_attach, pwmbus_attach), - DEVMETHOD(device_detach, pwmbus_detach), + DEVMETHOD(device_detach, bus_generic_detach), /* bus_if */ DEVMETHOD(bus_add_child, pwmbus_add_child), DEVMETHOD(bus_child_location, pwmbus_child_location), DEVMETHOD(bus_hinted_child, pwmbus_hinted_child), DEVMETHOD(bus_print_child, pwmbus_print_child), DEVMETHOD(bus_probe_nomatch, pwmbus_probe_nomatch), DEVMETHOD(bus_read_ivar, pwmbus_read_ivar), /* pwmbus_if */ DEVMETHOD(pwmbus_channel_count, pwmbus_channel_count), DEVMETHOD(pwmbus_channel_config, pwmbus_channel_config), DEVMETHOD(pwmbus_channel_get_config, pwmbus_channel_get_config), DEVMETHOD(pwmbus_channel_set_flags, pwmbus_channel_set_flags), DEVMETHOD(pwmbus_channel_get_flags, pwmbus_channel_get_flags), DEVMETHOD(pwmbus_channel_enable, pwmbus_channel_enable), DEVMETHOD(pwmbus_channel_is_enabled, pwmbus_channel_is_enabled), DEVMETHOD_END }; driver_t pwmbus_driver = { "pwmbus", pwmbus_methods, sizeof(struct pwmbus_softc), }; EARLY_DRIVER_MODULE(pwmbus, pwm, pwmbus_driver, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(pwmbus, 1); diff --git a/sys/dev/usb/misc/cp2112.c b/sys/dev/usb/misc/cp2112.c index 2492eae978bb..e3587241f412 100644 --- a/sys/dev/usb/misc/cp2112.c +++ b/sys/dev/usb/misc/cp2112.c @@ -1,1447 +1,1434 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) Andriy Gapon * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* * Hardware information links: * - CP2112 Datasheet * https://www.silabs.com/documents/public/data-sheets/cp2112-datasheet.pdf * - AN495: CP2112 Interface Specification * https://www.silabs.com/documents/public/application-notes/an495-cp2112-interface-specification.pdf * - CP2112 Errata * https://www.silabs.com/documents/public/errata/cp2112-errata.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR usb_debug #include #define SIZEOF_FIELD(_s, _f) sizeof(((struct _s *)NULL)->_f) #define CP2112GPIO_LOCK(sc) sx_xlock(&sc->gpio_lock) #define CP2112GPIO_UNLOCK(sc) sx_xunlock(&sc->gpio_lock) #define CP2112GPIO_LOCKED(sc) sx_assert(&sc->gpio_lock, SX_XLOCKED) #define CP2112_PART_NUM 0x0c #define CP2112_GPIO_COUNT 8 #define CP2112_REPORT_SIZE 64 #define CP2112_REQ_RESET 0x1 #define CP2112_REQ_GPIO_CFG 0x2 #define CP2112_REQ_GPIO_GET 0x3 #define CP2112_REQ_GPIO_SET 0x4 #define CP2112_REQ_VERSION 0x5 #define CP2112_REQ_SMB_CFG 0x6 #define CP2112_REQ_SMB_READ 0x10 #define CP2112_REQ_SMB_WRITE_READ 0x11 #define CP2112_REQ_SMB_READ_FORCE_SEND 0x12 #define CP2112_REQ_SMB_READ_RESPONSE 0x13 #define CP2112_REQ_SMB_WRITE 0x14 #define CP2112_REQ_SMB_XFER_STATUS_REQ 0x15 #define CP2112_REQ_SMB_XFER_STATUS_RESP 0x16 #define CP2112_REQ_SMB_CANCEL 0x17 #define CP2112_REQ_LOCK 0x20 #define CP2112_REQ_USB_CFG 0x21 #define CP2112_IIC_MAX_READ_LEN 512 #define CP2112_IIC_REPSTART_VER 2 /* Erratum CP2112_E10. */ #define CP2112_GPIO_SPEC_CLK7 1 /* Pin 7 is clock output. */ #define CP2112_GPIO_SPEC_TX0 2 /* Pin 0 pulses on USB TX. */ #define CP2112_GPIO_SPEC_RX1 4 /* Pin 1 pulses on USB RX. */ #define CP2112_IIC_STATUS0_IDLE 0 #define CP2112_IIC_STATUS0_BUSY 1 #define CP2112_IIC_STATUS0_CMP 2 #define CP2112_IIC_STATUS0_ERROR 3 #define CP2112_IIC_STATUS1_TIMEOUT_NACK 0 #define CP2112_IIC_STATUS1_TIMEOUT_BUS 1 #define CP2112_IIC_STATUS1_ARB_LOST 2 /* CP2112_REQ_VERSION */ struct version_request { uint8_t id; uint8_t part_num; uint8_t version; } __packed; /* CP2112_REQ_GPIO_GET */ struct gpio_get_req { uint8_t id; uint8_t state; } __packed; /* CP2112_REQ_GPIO_SET */ struct gpio_set_req { uint8_t id; uint8_t state; uint8_t mask; } __packed; /* CP2112_REQ_GPIO_CFG */ struct gpio_config_req { uint8_t id; uint8_t output; uint8_t pushpull; uint8_t special; uint8_t divider; } __packed; /* CP2112_REQ_SMB_XFER_STATUS_REQ */ struct i2c_xfer_status_req { uint8_t id; uint8_t request; } __packed; /* CP2112_REQ_SMB_XFER_STATUS_RESP */ struct i2c_xfer_status_resp { uint8_t id; uint8_t status0; uint8_t status1; uint16_t status2; uint16_t status3; } __packed; /* CP2112_REQ_SMB_READ_FORCE_SEND */ struct i2c_data_read_force_send_req { uint8_t id; uint16_t len; } __packed; /* CP2112_REQ_SMB_READ_RESPONSE */ struct i2c_data_read_resp { uint8_t id; uint8_t status; uint8_t len; uint8_t data[61]; } __packed; /* CP2112_REQ_SMB_READ */ struct i2c_write_read_req { uint8_t id; uint8_t slave; uint16_t rlen; uint8_t wlen; uint8_t wdata[16]; } __packed; /* CP2112_REQ_SMB_WRITE */ struct i2c_read_req { uint8_t id; uint8_t slave; uint16_t len; } __packed; /* CP2112_REQ_SMB_WRITE_READ */ struct i2c_write_req { uint8_t id; uint8_t slave; uint8_t len; uint8_t data[61]; } __packed; /* CP2112_REQ_SMB_CFG */ struct i2c_cfg_req { uint8_t id; uint32_t speed; /* Hz */ uint8_t slave_addr; /* ACK only */ uint8_t auto_send_read; /* boolean */ uint16_t write_timeout; /* 0-1000 ms, 0 ~ no timeout */ uint16_t read_timeout; /* 0-1000 ms, 0 ~ no timeout */ uint8_t scl_low_timeout;/* boolean */ uint16_t retry_count; /* 1-1000, 0 ~ forever */ } __packed; enum cp2112_out_mode { OUT_OD, OUT_PP, OUT_KEEP }; enum { CP2112_INTR_OUT = 0, CP2112_INTR_IN, CP2112_N_TRANSFER, }; struct cp2112_softc { device_t sc_gpio_dev; device_t sc_iic_dev; struct usb_device *sc_udev; uint8_t sc_iface_index; uint8_t sc_version; }; struct cp2112gpio_softc { struct sx gpio_lock; device_t busdev; int gpio_caps; struct gpio_pin pins[CP2112_GPIO_COUNT]; }; struct cp2112iic_softc { device_t dev; device_t iicbus_dev; struct usb_xfer *xfers[CP2112_N_TRANSFER]; u_char own_addr; struct { struct mtx lock; struct cv cv; struct { uint8_t *data; int len; int done; int error; } in; struct { const uint8_t *data; int len; int done; int error; } out; } io; }; -static int cp2112_detach(device_t dev); static int cp2112gpio_detach(device_t dev); static int cp2112iic_detach(device_t dev); static const STRUCT_USB_HOST_ID cp2112_devs[] = { { USB_VP(USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2112) }, { USB_VP(0x1009, USB_PRODUCT_SILABS_CP2112) }, /* XXX */ }; static int cp2112_get_report(device_t dev, uint8_t id, void *data, uint16_t len) { struct cp2112_softc *sc; int err; sc = device_get_softc(dev); err = usbd_req_get_report(sc->sc_udev, NULL, data, len, sc->sc_iface_index, UHID_FEATURE_REPORT, id); return (err); } static int cp2112_set_report(device_t dev, uint8_t id, void *data, uint16_t len) { struct cp2112_softc *sc; int err; sc = device_get_softc(dev); *(uint8_t *)data = id; err = usbd_req_set_report(sc->sc_udev, NULL, data, len, sc->sc_iface_index, UHID_FEATURE_REPORT, id); return (err); } static int cp2112_probe(device_t dev) { struct usb_attach_arg *uaa; uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bInterfaceClass != UICLASS_HID) return (ENXIO); if (usbd_lookup_id_by_uaa(cp2112_devs, sizeof(cp2112_devs), uaa) == 0) return (BUS_PROBE_DEFAULT); return (ENXIO); } static int cp2112_attach(device_t dev) { struct version_request vdata; struct usb_attach_arg *uaa; struct cp2112_softc *sc; int err; uaa = device_get_ivars(dev); sc = device_get_softc(dev); device_set_usb_desc(dev); sc->sc_udev = uaa->device; sc->sc_iface_index = uaa->info.bIfaceIndex; err = cp2112_get_report(dev, CP2112_REQ_VERSION, &vdata, sizeof(vdata)); if (err != 0) goto detach; device_printf(dev, "part number 0x%02x, version 0x%02x\n", vdata.part_num, vdata.version); if (vdata.part_num != CP2112_PART_NUM) { device_printf(dev, "unsupported part number\n"); goto detach; } sc->sc_version = vdata.version; sc->sc_gpio_dev = device_add_child(dev, "gpio", DEVICE_UNIT_ANY); if (sc->sc_gpio_dev != NULL) { err = device_probe_and_attach(sc->sc_gpio_dev); if (err != 0) { device_printf(dev, "failed to attach gpio child\n"); } } else { device_printf(dev, "failed to create gpio child\n"); } sc->sc_iic_dev = device_add_child(dev, "iichb", DEVICE_UNIT_ANY); if (sc->sc_iic_dev != NULL) { err = device_probe_and_attach(sc->sc_iic_dev); if (err != 0) { device_printf(dev, "failed to attach iic child\n"); } } else { device_printf(dev, "failed to create iic child\n"); } return (0); detach: - cp2112_detach(dev); + bus_generic_detach(dev); return (ENXIO); } -static int -cp2112_detach(device_t dev) -{ - int err; - - err = bus_generic_detach(dev); - if (err != 0) - return (err); - device_delete_children(dev); - return (0); -} - static int cp2112_gpio_read_pin(device_t dev, uint32_t pin_num, bool *on) { struct gpio_get_req data; struct cp2112gpio_softc *sc __diagused; int err; sc = device_get_softc(dev); CP2112GPIO_LOCKED(sc); err = cp2112_get_report(device_get_parent(dev), CP2112_REQ_GPIO_GET, &data, sizeof(data)); if (err != 0) return (err); *on = (data.state & ((uint8_t)1 << pin_num)) != 0; return (0); } static int cp2112_gpio_write_pin(device_t dev, uint32_t pin_num, bool on) { struct gpio_set_req data; struct cp2112gpio_softc *sc __diagused; int err; bool actual; sc = device_get_softc(dev); CP2112GPIO_LOCKED(sc); data.state = (uint8_t)on << pin_num; data.mask = (uint8_t)1 << pin_num; err = cp2112_set_report(device_get_parent(dev), CP2112_REQ_GPIO_SET, &data, sizeof(data)); if (err != 0) return (err); err = cp2112_gpio_read_pin(dev, pin_num, &actual); if (err != 0) return (err); if (actual != on) return (EIO); return (0); } static int cp2112_gpio_configure_write_pin(device_t dev, uint32_t pin_num, bool output, enum cp2112_out_mode *mode) { struct gpio_config_req data; struct cp2112gpio_softc *sc __diagused; int err; uint8_t mask; sc = device_get_softc(dev); CP2112GPIO_LOCKED(sc); err = cp2112_get_report(device_get_parent(dev), CP2112_REQ_GPIO_CFG, &data, sizeof(data)); if (err != 0) return (err); mask = (uint8_t)1 << pin_num; if (output) { data.output |= mask; switch (*mode) { case OUT_PP: data.pushpull |= mask; break; case OUT_OD: data.pushpull &= ~mask; break; default: break; } } else { data.output &= ~mask; } err = cp2112_set_report(device_get_parent(dev), CP2112_REQ_GPIO_CFG, &data, sizeof(data)); if (err != 0) return (err); /* Read back and verify. */ err = cp2112_get_report(device_get_parent(dev), CP2112_REQ_GPIO_CFG, &data, sizeof(data)); if (err != 0) return (err); if (((data.output & mask) != 0) != output) return (EIO); if (output) { switch (*mode) { case OUT_PP: if ((data.pushpull & mask) == 0) return (EIO); break; case OUT_OD: if ((data.pushpull & mask) != 0) return (EIO); break; default: *mode = (data.pushpull & mask) != 0 ? OUT_PP : OUT_OD; break; } } return (0); } static device_t cp2112_gpio_get_bus(device_t dev) { struct cp2112gpio_softc *sc; sc = device_get_softc(dev); return (sc->busdev); } static int cp2112_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = CP2112_GPIO_COUNT - 1; return (0); } static int cp2112_gpio_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value) { struct cp2112gpio_softc *sc; int err; if (pin_num >= CP2112_GPIO_COUNT) return (EINVAL); sc = device_get_softc(dev); CP2112GPIO_LOCK(sc); err = cp2112_gpio_write_pin(dev, pin_num, pin_value != 0); CP2112GPIO_UNLOCK(sc); return (err); } static int cp2112_gpio_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value) { struct cp2112gpio_softc *sc; int err; bool on; if (pin_num >= CP2112_GPIO_COUNT) return (EINVAL); sc = device_get_softc(dev); CP2112GPIO_LOCK(sc); err = cp2112_gpio_read_pin(dev, pin_num, &on); CP2112GPIO_UNLOCK(sc); if (err == 0) *pin_value = on; return (err); } static int cp2112_gpio_pin_toggle(device_t dev, uint32_t pin_num) { struct cp2112gpio_softc *sc; int err; bool on; if (pin_num >= CP2112_GPIO_COUNT) return (EINVAL); sc = device_get_softc(dev); CP2112GPIO_LOCK(sc); err = cp2112_gpio_read_pin(dev, pin_num, &on); if (err == 0) err = cp2112_gpio_write_pin(dev, pin_num, !on); CP2112GPIO_UNLOCK(sc); return (err); } static int cp2112_gpio_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *caps) { struct cp2112gpio_softc *sc; if (pin_num >= CP2112_GPIO_COUNT) return (EINVAL); sc = device_get_softc(dev); CP2112GPIO_LOCK(sc); *caps = sc->gpio_caps; CP2112GPIO_UNLOCK(sc); return (0); } static int cp2112_gpio_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *flags) { struct cp2112gpio_softc *sc; if (pin_num >= CP2112_GPIO_COUNT) return (EINVAL); sc = device_get_softc(dev); CP2112GPIO_LOCK(sc); *flags = sc->pins[pin_num].gp_flags; CP2112GPIO_UNLOCK(sc); return (0); } static int cp2112_gpio_pin_getname(device_t dev, uint32_t pin_num, char *name) { struct cp2112gpio_softc *sc; if (pin_num >= CP2112_GPIO_COUNT) return (EINVAL); sc = device_get_softc(dev); CP2112GPIO_LOCK(sc); memcpy(name, sc->pins[pin_num].gp_name, GPIOMAXNAME); CP2112GPIO_UNLOCK(sc); return (0); } static int cp2112_gpio_pin_setflags(device_t dev, uint32_t pin_num, uint32_t flags) { struct cp2112gpio_softc *sc; struct gpio_pin *pin; enum cp2112_out_mode out_mode; int err; if (pin_num >= CP2112_GPIO_COUNT) return (EINVAL); sc = device_get_softc(dev); if ((flags & sc->gpio_caps) != flags) return (EINVAL); if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == 0) return (EINVAL); if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) { return (EINVAL); } if ((flags & GPIO_PIN_INPUT) != 0) { if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) != 0) return (EINVAL); } else { if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) == (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) return (EINVAL); } /* * If neither push-pull or open-drain is explicitly requested, then * preserve the current state. */ out_mode = OUT_KEEP; if ((flags & GPIO_PIN_OUTPUT) != 0) { if ((flags & GPIO_PIN_OPENDRAIN) != 0) out_mode = OUT_OD; if ((flags & GPIO_PIN_PUSHPULL) != 0) out_mode = OUT_PP; } CP2112GPIO_LOCK(sc); pin = &sc->pins[pin_num]; err = cp2112_gpio_configure_write_pin(dev, pin_num, (flags & GPIO_PIN_OUTPUT) != 0, &out_mode); if (err == 0) { /* * If neither open-drain or push-pull was requested, then see * what hardware actually had. Otherwise, it has been * reconfigured as requested. */ if ((flags & GPIO_PIN_OUTPUT) != 0 && (flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) == 0) { KASSERT(out_mode != OUT_KEEP, ("impossible current output mode")); if (out_mode == OUT_OD) flags |= GPIO_PIN_OPENDRAIN; else flags |= GPIO_PIN_PUSHPULL; } pin->gp_flags = flags; } CP2112GPIO_UNLOCK(sc); return (err); } static int cp2112gpio_probe(device_t dev) { device_set_desc(dev, "CP2112 GPIO interface"); return (BUS_PROBE_SPECIFIC); } static int cp2112gpio_attach(device_t dev) { struct gpio_config_req data; struct cp2112gpio_softc *sc; device_t cp2112; int err; int i; uint8_t mask; cp2112 = device_get_parent(dev); sc = device_get_softc(dev); sx_init(&sc->gpio_lock, "cp2112 lock"); sc->gpio_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL; err = cp2112_get_report(cp2112, CP2112_REQ_GPIO_CFG, &data, sizeof(data)); if (err != 0) goto detach; for (i = 0; i < CP2112_GPIO_COUNT; i++) { struct gpio_pin *pin; mask = (uint8_t)1 << i; pin = &sc->pins[i]; pin->gp_flags = 0; snprintf(pin->gp_name, GPIOMAXNAME, "GPIO%u", i); pin->gp_name[GPIOMAXNAME - 1] = '\0'; if ((i == 0 && (data.special & CP2112_GPIO_SPEC_TX0) != 0) || (i == 1 && (data.special & CP2112_GPIO_SPEC_RX1) != 0) || (i == 7 && (data.special & CP2112_GPIO_SPEC_CLK7) != 0)) { /* Special mode means that a pin is not for GPIO. */ } else if ((data.output & mask) != 0) { pin->gp_flags |= GPIO_PIN_OUTPUT; if ((data.pushpull & mask) != 0) pin->gp_flags |= GPIO_PIN_PUSHPULL; else pin->gp_flags |= GPIO_PIN_OPENDRAIN; } else { pin->gp_flags |= GPIO_PIN_INPUT; } } sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) { device_printf(dev, "gpiobus_attach_bus failed\n"); goto detach; } return (0); detach: cp2112gpio_detach(dev); return (ENXIO); } static int cp2112gpio_detach(device_t dev) { struct cp2112gpio_softc *sc; sc = device_get_softc(dev); if (sc->busdev != NULL) gpiobus_detach_bus(dev); sx_destroy(&sc->gpio_lock); return (0); } static void cp2112iic_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cp2112iic_softc *sc; struct usb_page_cache *pc; sc = usbd_xfer_softc(xfer); mtx_assert(&sc->io.lock, MA_OWNED); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, sc->io.out.data, sc->io.out.len); usbd_xfer_set_frame_len(xfer, 0, sc->io.out.len); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); break; case USB_ST_TRANSFERRED: sc->io.out.error = 0; sc->io.out.done = 1; cv_signal(&sc->io.cv); break; default: /* Error */ device_printf(sc->dev, "write intr state %d error %d\n", USB_GET_STATE(xfer), error); sc->io.out.error = IIC_EBUSERR; cv_signal(&sc->io.cv); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); } break; } } static void cp2112iic_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct cp2112iic_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int act_len, len; mtx_assert(&sc->io.lock, MA_OWNED); usbd_xfer_status(xfer, &act_len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (sc->io.in.done) { device_printf(sc->dev, "interrupt while previous is pending, ignored\n"); } else if (sc->io.in.len == 0) { uint8_t buf[8]; /* * There is a spurious Transfer Status Response and * zero-length Read Response during hardware * configuration. Possibly they carry some information * about the initial bus state. */ if (device_is_attached(sc->dev)) { device_printf(sc->dev, "unsolicited interrupt, ignored\n"); if (bootverbose) { pc = usbd_xfer_get_frame(xfer, 0); len = MIN(sizeof(buf), act_len); usbd_copy_out(pc, 0, buf, len); device_printf(sc->dev, "data: %*D\n", len, buf, " "); } } else { pc = usbd_xfer_get_frame(xfer, 0); len = MIN(sizeof(buf), act_len); usbd_copy_out(pc, 0, buf, len); if (buf[0] == CP2112_REQ_SMB_XFER_STATUS_RESP) { device_printf(sc->dev, "initial bus status0 = 0x%02x, " "status1 = 0x%02x\n", buf[1], buf[2]); } } } else if (act_len == CP2112_REPORT_SIZE) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, sc->io.in.data, sc->io.in.len); sc->io.in.error = 0; sc->io.in.done = 1; } else { device_printf(sc->dev, "unexpected input report length %u\n", act_len); sc->io.in.error = IIC_EBUSERR; sc->io.in.done = 1; } cv_signal(&sc->io.cv); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ device_printf(sc->dev, "read intr state %d error %d\n", USB_GET_STATE(xfer), error); sc->io.in.error = IIC_EBUSERR; sc->io.in.done = 1; cv_signal(&sc->io.cv); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static const struct usb_config cp2112iic_config[CP2112_N_TRANSFER] = { [CP2112_INTR_OUT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .flags = { .pipe_bof = 1, .no_pipe_ok = 1, }, .bufsize = 0, /* use wMaxPacketSize */ .callback = &cp2112iic_intr_write_callback, }, [CP2112_INTR_IN] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 1, .short_xfer_ok = 1, }, .bufsize = 0, /* use wMaxPacketSize */ .callback = &cp2112iic_intr_read_callback, }, }; static int cp2112iic_send_req(struct cp2112iic_softc *sc, const void *data, uint16_t len) { int err; mtx_assert(&sc->io.lock, MA_OWNED); KASSERT(sc->io.out.done == 0, ("%s: conflicting request", __func__)); sc->io.out.data = data; sc->io.out.len = len; DTRACE_PROBE1(send__req, uint8_t, *(const uint8_t *)data); usbd_transfer_start(sc->xfers[CP2112_INTR_OUT]); while (!sc->io.out.done) cv_wait(&sc->io.cv, &sc->io.lock); usbd_transfer_stop(sc->xfers[CP2112_INTR_OUT]); sc->io.out.done = 0; sc->io.out.data = NULL; sc->io.out.len = 0; err = sc->io.out.error; if (err != 0) { device_printf(sc->dev, "output report 0x%02x failed: %d\n", *(const uint8_t*)data, err); } return (err); } static int cp2112iic_req_resp(struct cp2112iic_softc *sc, const void *req_data, uint16_t req_len, void *resp_data, uint16_t resp_len) { int err; mtx_assert(&sc->io.lock, MA_OWNED); /* * Prepare to receive a response interrupt even before the * request transfer is confirmed (USB_ST_TRANSFERED). */ KASSERT(sc->io.in.done == 0, ("%s: conflicting request", __func__)); sc->io.in.len = resp_len; sc->io.in.data = resp_data; err = cp2112iic_send_req(sc, req_data, req_len); if (err != 0) { sc->io.in.len = 0; sc->io.in.data = NULL; return (err); } while (!sc->io.in.done) cv_wait(&sc->io.cv, &sc->io.lock); err = sc->io.in.error; sc->io.in.done = 0; sc->io.in.error = 0; sc->io.in.len = 0; sc->io.in.data = NULL; return (err); } static int cp2112iic_check_req_status(struct cp2112iic_softc *sc) { struct i2c_xfer_status_req xfer_status_req; struct i2c_xfer_status_resp xfer_status_resp; int err; mtx_assert(&sc->io.lock, MA_OWNED); do { xfer_status_req.id = CP2112_REQ_SMB_XFER_STATUS_REQ; xfer_status_req.request = 1; err = cp2112iic_req_resp(sc, &xfer_status_req, sizeof(xfer_status_req), &xfer_status_resp, sizeof(xfer_status_resp)); if (xfer_status_resp.id != CP2112_REQ_SMB_XFER_STATUS_RESP) { device_printf(sc->dev, "unexpected response 0x%02x to status request\n", xfer_status_resp.id); err = IIC_EBUSERR; goto out; } DTRACE_PROBE4(xfer__status, uint8_t, xfer_status_resp.status0, uint8_t, xfer_status_resp.status1, uint16_t, be16toh(xfer_status_resp.status2), uint16_t, be16toh(xfer_status_resp.status3)); switch (xfer_status_resp.status0) { case CP2112_IIC_STATUS0_IDLE: err = IIC_ESTATUS; break; case CP2112_IIC_STATUS0_BUSY: err = ERESTART; /* non-I2C, special handling */ break; case CP2112_IIC_STATUS0_CMP: err = IIC_NOERR; break; case CP2112_IIC_STATUS0_ERROR: switch (xfer_status_resp.status1) { case CP2112_IIC_STATUS1_TIMEOUT_NACK: err = IIC_ENOACK; break; case CP2112_IIC_STATUS1_TIMEOUT_BUS: err = IIC_ETIMEOUT; break; case CP2112_IIC_STATUS1_ARB_LOST: err = IIC_EBUSBSY; break; default: device_printf(sc->dev, "i2c error, status = 0x%02x\n", xfer_status_resp.status1); err = IIC_ESTATUS; break; } break; default: device_printf(sc->dev, "unknown i2c xfer status0 0x%02x\n", xfer_status_resp.status0); err = IIC_EBUSERR; break; } } while (err == ERESTART); out: return (err); } static int cp2112iic_read_data(struct cp2112iic_softc *sc, void *data, uint16_t in_len, uint16_t *out_len) { struct i2c_data_read_force_send_req data_read_force_send; struct i2c_data_read_resp data_read_resp; int err; mtx_assert(&sc->io.lock, MA_OWNED); /* * Prepare to receive a response interrupt even before the request * transfer is confirmed (USB_ST_TRANSFERED). */ if (in_len > sizeof(data_read_resp.data)) in_len = sizeof(data_read_resp.data); data_read_force_send.id = CP2112_REQ_SMB_READ_FORCE_SEND; data_read_force_send.len = htobe16(in_len); err = cp2112iic_req_resp(sc, &data_read_force_send, sizeof(data_read_force_send), &data_read_resp, sizeof(data_read_resp)); if (err != 0) goto out; if (data_read_resp.id != CP2112_REQ_SMB_READ_RESPONSE) { device_printf(sc->dev, "unexpected response 0x%02x to data read request\n", data_read_resp.id); err = IIC_EBUSERR; goto out; } DTRACE_PROBE2(read__response, uint8_t, data_read_resp.status, uint8_t, data_read_resp.len); /* * We expect either the request completed status or, more typical for * this driver, the bus idle status because of the preceding * Force Read Status command (which is not an I2C request). */ if (data_read_resp.status != CP2112_IIC_STATUS0_CMP && data_read_resp.status != CP2112_IIC_STATUS0_IDLE) { err = IIC_EBUSERR; goto out; } if (data_read_resp.len > in_len) { device_printf(sc->dev, "device returns more data than asked\n"); err = IIC_EOVERFLOW; goto out; } *out_len = data_read_resp.len; if (*out_len > 0) memcpy(data, data_read_resp.data, *out_len); out: return (err); } static int cp2112iic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct cp2112iic_softc *sc = device_get_softc(dev); struct cp2112_softc *psc = device_get_softc(device_get_parent(dev)); const char *reason = NULL; uint32_t i; uint16_t read_off, to_read; int err; /* * The hardware interface imposes limits on allowed I2C messages. * It is not possible to explicitly send a start or stop. * It is not possible to do a zero length transfer. * For this reason it's impossible to send a message with no data * at all (like an SMBus quick message). * Each read or write transfer beginning with the start condition * and ends with the stop condition. The only exception is that * it is possible to have a write transfer followed by a read * transfer to the same slave with the repeated start condition * between them. */ for (i = 0; i < nmsgs; i++) { if (i == 0 && (msgs[i].flags & IIC_M_NOSTART) != 0) { reason = "first message without start"; break; } if (i == nmsgs - 1 && (msgs[i].flags & IIC_M_NOSTOP) != 0) { reason = "last message without stop"; break; } if (msgs[i].len == 0) { reason = "message with no data"; break; } if ((msgs[i].flags & IIC_M_RD) != 0 && msgs[i].len > CP2112_IIC_MAX_READ_LEN) { reason = "too long read"; break; } if ((msgs[i].flags & IIC_M_RD) == 0 && msgs[i].len > SIZEOF_FIELD(i2c_write_req, data)) { reason = "too long write"; break; } if ((msgs[i].flags & IIC_M_NOSTART) != 0) { reason = "message without start or repeated start"; break; } if ((msgs[i].flags & IIC_M_NOSTOP) != 0 && (msgs[i].flags & IIC_M_RD) != 0) { reason = "read without stop"; break; } if ((msgs[i].flags & IIC_M_NOSTOP) != 0 && psc->sc_version < CP2112_IIC_REPSTART_VER) { reason = "write without stop"; break; } if ((msgs[i].flags & IIC_M_NOSTOP) != 0 && msgs[i].len > SIZEOF_FIELD(i2c_write_read_req, wdata)) { reason = "too long write without stop"; break; } if (i > 0) { if ((msgs[i - 1].flags & IIC_M_NOSTOP) != 0 && msgs[i].slave != msgs[i - 1].slave) { reason = "change of slave without stop"; break; } if ((msgs[i - 1].flags & IIC_M_NOSTOP) != 0 && (msgs[i].flags & IIC_M_RD) == 0) { reason = "write after repeated start"; break; } } } if (reason != NULL) { if (bootverbose) device_printf(dev, "unsupported i2c message: %s\n", reason); return (IIC_ENOTSUPP); } mtx_lock(&sc->io.lock); for (i = 0; i < nmsgs; i++) { if (i + 1 < nmsgs && (msgs[i].flags & IIC_M_NOSTOP) != 0) { /* * Combine into a single * CP2112 operation. */ struct i2c_write_read_req req; KASSERT((msgs[i].flags & IIC_M_RD) == 0, ("read without stop")); KASSERT((msgs[i + 1].flags & IIC_M_RD) != 0, ("write after write without stop")); req.id = CP2112_REQ_SMB_WRITE_READ; req.slave = msgs[i].slave & ~LSB; to_read = msgs[i + 1].len; req.rlen = htobe16(to_read); req.wlen = msgs[i].len; memcpy(req.wdata, msgs[i].buf, msgs[i].len); err = cp2112iic_send_req(sc, &req, msgs[i].len + 5); /* * The next message is already handled. * Also needed for read data to go into the right msg. */ i++; } else if ((msgs[i].flags & IIC_M_RD) != 0) { struct i2c_read_req req; req.id = CP2112_REQ_SMB_READ; req.slave = msgs[i].slave & ~LSB; to_read = msgs[i].len; req.len = htobe16(to_read); err = cp2112iic_send_req(sc, &req, sizeof(req)); } else { struct i2c_write_req req; req.id = CP2112_REQ_SMB_WRITE; req.slave = msgs[i].slave & ~LSB; req.len = msgs[i].len; memcpy(req.data, msgs[i].buf, msgs[i].len); to_read = 0; err = cp2112iic_send_req(sc, &req, msgs[i].len + 3); } if (err != 0) break; err = cp2112iic_check_req_status(sc); if (err != 0) break; read_off = 0; while (to_read > 0) { uint16_t act_read; err = cp2112iic_read_data(sc, msgs[i].buf + read_off, to_read, &act_read); if (err != 0) break; KASSERT(act_read <= to_read, ("cp2112iic_read_data " "returned more data than asked")); read_off += act_read; to_read -= act_read; } if (err != 0) break; } mtx_unlock(&sc->io.lock); return (err); } static int cp2112iic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct i2c_cfg_req i2c_cfg; struct cp2112iic_softc *sc; device_t cp2112; u_int busfreq; int err; sc = device_get_softc(dev); cp2112 = device_get_parent(dev); if (sc->iicbus_dev == NULL) busfreq = 100000; else busfreq = IICBUS_GET_FREQUENCY(sc->iicbus_dev, speed); err = cp2112_get_report(cp2112, CP2112_REQ_SMB_CFG, &i2c_cfg, sizeof(i2c_cfg)); if (err != 0) { device_printf(dev, "failed to get CP2112_REQ_SMB_CFG report\n"); return (err); } if (oldaddr != NULL) *oldaddr = i2c_cfg.slave_addr; /* * For simplicity we do not enable Auto Send Read * because of erratum CP2112_E101 (fixed in version 3). * * TODO: set I2C parameters based on configuration preferences: * - read and write timeouts (no timeout by default), * - SCL low timeout (disabled by default), * etc. * * TODO: should the device reset request (0x01) be sent? * If the device disconnects as a result, then no. */ i2c_cfg.speed = htobe32(busfreq); if (addr != 0) i2c_cfg.slave_addr = addr; i2c_cfg.auto_send_read = 0; i2c_cfg.retry_count = htobe16(1); i2c_cfg.scl_low_timeout = 0; if (bootverbose) { device_printf(dev, "speed %d Hz\n", be32toh(i2c_cfg.speed)); device_printf(dev, "slave addr 0x%02x\n", i2c_cfg.slave_addr); device_printf(dev, "auto send read %s\n", i2c_cfg.auto_send_read ? "on" : "off"); device_printf(dev, "write timeout %d ms (0 - disabled)\n", be16toh(i2c_cfg.write_timeout)); device_printf(dev, "read timeout %d ms (0 - disabled)\n", be16toh(i2c_cfg.read_timeout)); device_printf(dev, "scl low timeout %s\n", i2c_cfg.scl_low_timeout ? "on" : "off"); device_printf(dev, "retry count %d (0 - no limit)\n", be16toh(i2c_cfg.retry_count)); } err = cp2112_set_report(cp2112, CP2112_REQ_SMB_CFG, &i2c_cfg, sizeof(i2c_cfg)); if (err != 0) { device_printf(dev, "failed to set CP2112_REQ_SMB_CFG report\n"); return (err); } return (0); } static int cp2112iic_probe(device_t dev) { device_set_desc(dev, "CP2112 I2C interface"); return (BUS_PROBE_SPECIFIC); } static int cp2112iic_attach(device_t dev) { struct cp2112iic_softc *sc; struct cp2112_softc *psc; device_t cp2112; int err; sc = device_get_softc(dev); sc->dev = dev; cp2112 = device_get_parent(dev); psc = device_get_softc(cp2112); mtx_init(&sc->io.lock, "cp2112iic lock", NULL, MTX_DEF | MTX_RECURSE); cv_init(&sc->io.cv, "cp2112iic cv"); err = usbd_transfer_setup(psc->sc_udev, &psc->sc_iface_index, sc->xfers, cp2112iic_config, nitems(cp2112iic_config), sc, &sc->io.lock); if (err != 0) { device_printf(dev, "usbd_transfer_setup failed %d\n", err); goto detach; } /* Prepare to receive interrupts. */ mtx_lock(&sc->io.lock); usbd_transfer_start(sc->xfers[CP2112_INTR_IN]); mtx_unlock(&sc->io.lock); sc->iicbus_dev = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY); if (sc->iicbus_dev == NULL) { device_printf(dev, "iicbus creation failed\n"); err = ENXIO; goto detach; } bus_attach_children(dev); return (0); detach: cp2112iic_detach(dev); return (err); } static int cp2112iic_detach(device_t dev) { struct cp2112iic_softc *sc; int err; sc = device_get_softc(dev); err = bus_generic_detach(dev); if (err != 0) return (err); device_delete_children(dev); mtx_lock(&sc->io.lock); usbd_transfer_stop(sc->xfers[CP2112_INTR_IN]); mtx_unlock(&sc->io.lock); usbd_transfer_unsetup(sc->xfers, nitems(cp2112iic_config)); cv_destroy(&sc->io.cv); mtx_destroy(&sc->io.lock); return (0); } static device_method_t cp2112hid_methods[] = { DEVMETHOD(device_probe, cp2112_probe), DEVMETHOD(device_attach, cp2112_attach), - DEVMETHOD(device_detach, cp2112_detach), + DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD_END }; static driver_t cp2112hid_driver = { .name = "cp2112hid", .methods = cp2112hid_methods, .size = sizeof(struct cp2112_softc), }; DRIVER_MODULE(cp2112hid, uhub, cp2112hid_driver, NULL, NULL); MODULE_DEPEND(cp2112hid, usb, 1, 1, 1); MODULE_VERSION(cp2112hid, 1); USB_PNP_HOST_INFO(cp2112_devs); static device_method_t cp2112gpio_methods[] = { /* Device */ DEVMETHOD(device_probe, cp2112gpio_probe), DEVMETHOD(device_attach, cp2112gpio_attach), DEVMETHOD(device_detach, cp2112gpio_detach), /* GPIO */ DEVMETHOD(gpio_get_bus, cp2112_gpio_get_bus), DEVMETHOD(gpio_pin_max, cp2112_gpio_pin_max), DEVMETHOD(gpio_pin_get, cp2112_gpio_pin_get), DEVMETHOD(gpio_pin_set, cp2112_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, cp2112_gpio_pin_toggle), DEVMETHOD(gpio_pin_getname, cp2112_gpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, cp2112_gpio_pin_getcaps), DEVMETHOD(gpio_pin_getflags, cp2112_gpio_pin_getflags), DEVMETHOD(gpio_pin_setflags, cp2112_gpio_pin_setflags), DEVMETHOD_END }; static driver_t cp2112gpio_driver = { .name = "gpio", .methods = cp2112gpio_methods, .size = sizeof(struct cp2112gpio_softc), }; DRIVER_MODULE(cp2112gpio, cp2112hid, cp2112gpio_driver, NULL, NULL); MODULE_DEPEND(cp2112gpio, cp2112hid, 1, 1, 1); MODULE_DEPEND(cp2112gpio, gpiobus, 1, 1, 1); MODULE_VERSION(cp2112gpio, 1); static device_method_t cp2112iic_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cp2112iic_probe), DEVMETHOD(device_attach, cp2112iic_attach), DEVMETHOD(device_detach, cp2112iic_detach), /* I2C methods */ DEVMETHOD(iicbus_transfer, cp2112iic_transfer), DEVMETHOD(iicbus_reset, cp2112iic_reset), DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD_END }; static driver_t cp2112iic_driver = { "iichb", cp2112iic_methods, sizeof(struct cp2112iic_softc) }; DRIVER_MODULE(cp2112iic, cp2112hid, cp2112iic_driver, NULL, NULL); MODULE_DEPEND(cp2112iic, cp2112hid, 1, 1, 1); MODULE_DEPEND(cp2112iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(cp2112iic, 1);