From owner-svn-src-stable-11@freebsd.org Mon Sep 11 02:50:25 2017 Return-Path: Delivered-To: svn-src-stable-11@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id CEAC7E143FB; Mon, 11 Sep 2017 02:50:25 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 9C1C96BDE2; Mon, 11 Sep 2017 02:50:25 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id v8B2oO57077157; Mon, 11 Sep 2017 02:50:24 GMT (envelope-from ian@FreeBSD.org) Received: (from ian@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id v8B2oOX2077152; Mon, 11 Sep 2017 02:50:24 GMT (envelope-from ian@FreeBSD.org) Message-Id: <201709110250.v8B2oOX2077152@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: ian set sender to ian@FreeBSD.org using -f From: Ian Lepore Date: Mon, 11 Sep 2017 02:50:24 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-11@freebsd.org Subject: svn commit: r323419 - in stable/11/sys: arm/freescale/imx conf dev/iicbus X-SVN-Group: stable-11 X-SVN-Commit-Author: ian X-SVN-Commit-Paths: in stable/11/sys: arm/freescale/imx conf dev/iicbus X-SVN-Commit-Revision: 323419 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-stable-11@freebsd.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: SVN commit messages for only the 11-stable src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 11 Sep 2017 02:50:25 -0000 Author: ian Date: Mon Sep 11 02:50:24 2017 New Revision: 323419 URL: https://svnweb.freebsd.org/changeset/base/323419 Log: MFC r320460, r320461, r320462, r320463: If an i2c transfer ends due to error, issue a stop on the bus even if the nostop option is set, if a start was issued. Add iic_recover_bus(), a helper function that can be used by any i2c driver which is able to manipulate the clock and data lines directly. When an i2c bus is hung by a slave device stuck in the middle of a transaction that didn't complete properly, this function manipulates the clock and data lines in a sequence known to reliably reset slave devices. The most common cause of a hung i2c bus is a system reboot in the middle of an i2c transfer (so it doesnt' happen often, but now there is a way other than power cycling to recover from it). Add bus recovery handling to the imx5/imx6 i2c driver. Added: stable/11/sys/dev/iicbus/iic_recover_bus.c - copied unchanged from r320461, head/sys/dev/iicbus/iic_recover_bus.c stable/11/sys/dev/iicbus/iic_recover_bus.h - copied unchanged from r320461, head/sys/dev/iicbus/iic_recover_bus.h Modified: stable/11/sys/arm/freescale/imx/imx_i2c.c stable/11/sys/conf/files stable/11/sys/dev/iicbus/iiconf.c Directory Properties: stable/11/ (props changed) Modified: stable/11/sys/arm/freescale/imx/imx_i2c.c ============================================================================== --- stable/11/sys/arm/freescale/imx/imx_i2c.c Mon Sep 11 02:38:57 2017 (r323418) +++ stable/11/sys/arm/freescale/imx/imx_i2c.c Mon Sep 11 02:50:24 2017 (r323419) @@ -48,6 +48,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -61,6 +62,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include "iicbus_if.h" #include @@ -68,6 +70,9 @@ __FBSDID("$FreeBSD$"); #include #include +#include +#include + #define I2C_ADDR_REG 0x00 /* I2C slave address register */ #define I2C_FDR_REG 0x04 /* I2C frequency divider register */ #define I2C_CONTROL_REG 0x08 /* I2C control register */ @@ -131,6 +136,9 @@ struct i2c_softc { struct resource *res; int rid; sbintime_t byte_time_sbt; + int rb_pinctl_idx; + gpio_pin_t rb_sclpin; + gpio_pin_t rb_sdapin; }; static phandle_t i2c_get_node(device_t, device_t); @@ -275,6 +283,68 @@ i2c_error_handler(struct i2c_softc *sc, int error) } static int +i2c_recover_getsda(void *ctx) +{ + bool active; + + gpio_pin_is_active(((struct i2c_softc *)ctx)->rb_sdapin, &active); + return (active); +} + +static void +i2c_recover_setsda(void *ctx, int value) +{ + + gpio_pin_set_active(((struct i2c_softc *)ctx)->rb_sdapin, value); +} + +static int +i2c_recover_getscl(void *ctx) +{ + bool active; + + gpio_pin_is_active(((struct i2c_softc *)ctx)->rb_sclpin, &active); + return (active); + +} + +static void +i2c_recover_setscl(void *ctx, int value) +{ + + gpio_pin_set_active(((struct i2c_softc *)ctx)->rb_sclpin, value); +} + +static int +i2c_recover_bus(struct i2c_softc *sc) +{ + struct iicrb_pin_access pins; + int err; + + /* + * If we have gpio pinmux config, reconfigure the pins to gpio mode, + * invoke iic_recover_bus which checks for a hung bus and bitbangs a + * recovery sequence if necessary, then configure the pins back to i2c + * mode (idx 0). + */ + if (sc->rb_pinctl_idx == 0) + return (0); + + fdt_pinctrl_configure(sc->dev, sc->rb_pinctl_idx); + + pins.ctx = sc; + pins.getsda = i2c_recover_getsda; + pins.setsda = i2c_recover_setsda; + pins.getscl = i2c_recover_getscl; + pins.setscl = i2c_recover_setscl; + err = iic_recover_bus(&pins); + + fdt_pinctrl_configure(sc->dev, 0); + + return (err); +} + +static int i2c_probe(device_t dev) { @@ -292,7 +362,10 @@ i2c_probe(device_t dev) static int i2c_attach(device_t dev) { + char wrkstr[16]; struct i2c_softc *sc; + phandle_t node; + int err, cfgidx; sc = device_get_softc(dev); sc->dev = dev; @@ -311,6 +384,49 @@ i2c_attach(device_t dev) return (ENXIO); } + /* + * Set up for bus recovery using gpio pins, if the pinctrl and gpio + * properties are present. This is optional. If all the config data is + * not in place, we just don't do gpio bitbang bus recovery. + */ + node = ofw_bus_get_node(sc->dev); + + err = gpio_pin_get_by_ofw_property(dev, node, "scl-gpios", + &sc->rb_sclpin); + if (err != 0) + goto no_recovery; + err = gpio_pin_get_by_ofw_property(dev, node, "sda-gpios", + &sc->rb_sdapin); + if (err != 0) + goto no_recovery; + + /* + * Preset the gpio pins to output high (idle bus state). The signal + * won't actually appear on the pins until the bus recovery code changes + * the pinmux config from i2c to gpio. + */ + gpio_pin_setflags(sc->rb_sclpin, GPIO_PIN_OUTPUT); + gpio_pin_setflags(sc->rb_sdapin, GPIO_PIN_OUTPUT); + gpio_pin_set_active(sc->rb_sclpin, true); + gpio_pin_set_active(sc->rb_sdapin, true); + + /* + * Obtain the index of pinctrl node for bus recovery using gpio pins, + * then confirm that pinctrl properties exist for that index and for the + * default pinctrl-0. If sc->rb_pinctl_idx is non-zero, the reset code + * will also do a bus recovery, so setting this value must be last. + */ + err = ofw_bus_find_string_index(node, "pinctrl-names", "gpio", &cfgidx); + if (err == 0) { + snprintf(wrkstr, sizeof(wrkstr), "pinctrl-%d", cfgidx); + if (OF_hasprop(node, "pinctrl-0") && OF_hasprop(node, wrkstr)) + sc->rb_pinctl_idx = cfgidx; + } + +no_recovery: + + /* We don't do a hardware reset here because iicbus_attach() does it. */ + bus_generic_attach(dev); return (0); } @@ -340,7 +456,7 @@ i2c_repeated_start(device_t dev, u_char slave, int tim } static int -i2c_start(device_t dev, u_char slave, int timeout) +i2c_start_ll(device_t dev, u_char slave, int timeout) { struct i2c_softc *sc; int error; @@ -362,6 +478,31 @@ i2c_start(device_t dev, u_char slave, int timeout) } static int +i2c_start(device_t dev, u_char slave, int timeout) +{ + struct i2c_softc *sc; + int error; + + sc = device_get_softc(dev); + + /* + * Invoke the low-level code to put the bus into master mode and address + * the given slave. If that fails, idle the controller and attempt a + * bus recovery, and then try again one time. Signaling a start and + * addressing the slave is the only operation that a low-level driver + * can safely retry without any help from the upper layers that know + * more about the slave device. + */ + if ((error = i2c_start_ll(dev, slave, timeout)) != 0) { + i2c_write_reg(sc, I2C_CONTROL_REG, 0x0); + if ((error = i2c_recover_bus(sc)) != 0) + return (error); + error = i2c_start_ll(dev, slave, timeout); + } + return (error); +} + +static int i2c_stop(device_t dev) { struct i2c_softc *sc; @@ -410,7 +551,12 @@ i2c_reset(device_t dev, u_char speed, u_char addr, u_c i2c_write_reg(sc, I2C_STATUS_REG, 0x0); i2c_write_reg(sc, I2C_CONTROL_REG, 0x0); i2c_write_reg(sc, I2C_FDR_REG, (uint8_t)clkdiv_table[i].regcode); - return (IIC_NOERR); + + /* + * Now that the controller is idle, perform bus recovery. If the bus + * isn't hung, this a fairly fast no-op. + */ + return (i2c_recover_bus(sc)); } static int Modified: stable/11/sys/conf/files ============================================================================== --- stable/11/sys/conf/files Mon Sep 11 02:38:57 2017 (r323418) +++ stable/11/sys/conf/files Mon Sep 11 02:50:24 2017 (r323419) @@ -1693,6 +1693,7 @@ dev/iicbus/ds3231.c optional ds3231 dev/iicbus/icee.c optional icee dev/iicbus/if_ic.c optional ic dev/iicbus/iic.c optional iic +dev/iicbus/iic_recover_bus.c optional iicbus dev/iicbus/iicbb.c optional iicbb dev/iicbus/iicbb_if.m optional iicbb dev/iicbus/iicbus.c optional iicbus Copied: stable/11/sys/dev/iicbus/iic_recover_bus.c (from r320461, head/sys/dev/iicbus/iic_recover_bus.c) ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ stable/11/sys/dev/iicbus/iic_recover_bus.c Mon Sep 11 02:50:24 2017 (r323419, copy of r320461, head/sys/dev/iicbus/iic_recover_bus.c) @@ -0,0 +1,124 @@ +/*- + * Copyright (c) 2017 Ian Lepore + * All rights reserved. + * + * Development sponsored by Microsemi, Inc. + * + * 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 +__FBSDID("$FreeBSD$"); + +/* + * Helper code to recover a hung i2c bus by bit-banging a recovery sequence. + * + * An i2c bus can be hung by a slave driving the clock (rare) or data lines low. + * The most common cause is a partially-completed transaction such as rebooting + * while a slave is sending a byte of data. Because i2c allows the clock to + * freeze for any amount of time, the slave device will continue driving the + * data line until power is removed, or the clock cycles enough times to + * complete the current byte. After completing any partial byte, a START/STOP + * sequence resets the slave and the bus is recovered. + * + * Any i2c driver which is able to manually set the level of the clock and data + * lines can use this common code for bus recovery. On many SOCs that have + * embedded i2c controllers, the i2c pins can be temporarily reassigned as gpio + * pins to do the bus recovery, then can be assigned back to the i2c hardware. + */ + +#include "opt_platform.h" + +#include +#include +#include + +#include +#include + +int +iic_recover_bus(struct iicrb_pin_access *pins) +{ + const u_int timeout_us = 40000; + const u_int delay_us = 500; + int i; + + /* + * Start with clock and data high. + */ + pins->setsda(pins->ctx, 1); + pins->setscl(pins->ctx, 1); + + /* + * At this point, SCL should be high. If it's not, some slave on the + * bus is doing clock-stretching and we should wait a while. If that + * slave is completely locked up there may be no way to recover at all. + * We wait up to 40 milliseconds, a seriously pessimistic time (even a + * cheap eeprom has a max post-write delay of only 10ms), and also long + * enough to allow SMB slaves to timeout normally after 35ms. + */ + for (i = 0; i < timeout_us; i += delay_us) { + if (pins->getscl(pins->ctx)) + break; + DELAY(delay_us); + } + if (i >= timeout_us) + return (IIC_EBUSERR); + + /* + * At this point we should be able to control the clock line. Some + * slave may be part way through a byte transfer, and could be holding + * the data line low waiting for more clock pulses to finish the byte. + * Cycle the clock until we see the data line go high, but only up to 9 + * times because if it's not free after 9 clocks we're never going to + * win this battle. We do 9 max because that's a byte plus an ack/nack + * bit, after which the slave must not be driving the data line anymore. + */ + for (i = 0; ; ++i) { + if (pins->getsda(pins->ctx)) + break; + if (i == 9) + return (IIC_EBUSERR); + pins->setscl(pins->ctx, 0); + DELAY(5); + pins->setscl(pins->ctx, 1); + DELAY(5); + } + + /* + * At this point we should be in control of both the clock and data + * lines, and both lines should be high. To complete the reset of a + * slave that was part way through a transaction, we need to do a + * START/STOP sequence, which leaves both lines high at the end. + * - START: SDA transitions high->low while SCL remains high. + * - STOP: SDA transitions low->high while SCL remains high. + * Note that even though the clock line remains high, we transition the + * data line no faster than it would change state with a 100khz clock. + */ + pins->setsda(pins->ctx, 0); + DELAY(5); + pins->setsda(pins->ctx, 1); + DELAY(5); + + return (0); +} + Copied: stable/11/sys/dev/iicbus/iic_recover_bus.h (from r320461, head/sys/dev/iicbus/iic_recover_bus.h) ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ stable/11/sys/dev/iicbus/iic_recover_bus.h Mon Sep 11 02:50:24 2017 (r323419, copy of r320461, head/sys/dev/iicbus/iic_recover_bus.h) @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2017 Ian Lepore + * All rights reserved. + * + * Development sponsored by Microsemi, Inc. + * + * 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. + * + * $FreeBSD$ + */ + +/* + * Helper code to recover a hung i2c bus by bit-banging a recovery sequence. + */ + +#ifndef _IICBUS_IIC_RECOVER_BUS_H_ +#define _IICBUS_IIC_RECOVER_BUS_H_ + +struct iicrb_pin_access { + void *ctx; + int (*getsda)(void *ctx); + void (*setsda)(void *ctx, int value); + int (*getscl)(void *ctx); + void (*setscl)(void *ctx, int value); +}; + +/* + * Drive the bus-recovery logic by manipulating the line states using the + * caller-provided functions. This does not block or sleep or acquire any locks + * (unless the provided pin access functions do so). It uses DELAY() to pace + * bits on the bus. + * + * Returns 0 if the bus is functioning properly or IIC_EBUSERR if the recovery + * attempt failed and some slave device is still driving the bus. + */ +int iic_recover_bus(struct iicrb_pin_access *pins); + +#endif Modified: stable/11/sys/dev/iicbus/iiconf.c ============================================================================== --- stable/11/sys/dev/iicbus/iiconf.c Mon Sep 11 02:38:57 2017 (r323418) +++ stable/11/sys/dev/iicbus/iiconf.c Mon Sep 11 02:50:24 2017 (r323419) @@ -419,7 +419,7 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs { int i, error, lenread, lenwrote, nkid, rpstart, addr; device_t *children, bus; - bool nostop; + bool nostop, started; if ((error = device_get_children(dev, &children, &nkid)) != 0) return (IIC_ERESOURCE); @@ -431,6 +431,7 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs rpstart = 0; free(children, M_TEMP); nostop = iicbus_get_nostop(dev); + started = false; for (i = 0, error = 0; i < nmsgs && error == 0; i++) { addr = msgs[i].slave; if (msgs[i].flags & IIC_M_RD) @@ -443,9 +444,10 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs error = iicbus_repeated_start(bus, addr, 0); else error = iicbus_start(bus, addr, 0); + if (error != 0) + break; + started = true; } - if (error != 0) - break; if (msgs[i].flags & IIC_M_RD) error = iicbus_read(bus, msgs[i].buf, msgs[i].len, @@ -464,7 +466,7 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs iicbus_stop(bus); } } - if (error != 0 && !nostop) + if (error != 0 && started) iicbus_stop(bus); return (error); }