Author: wulf
Date: Sun Nov  3 21:08:26 2019
New Revision: 354315
URL: https://svnweb.freebsd.org/changeset/base/354315

Log:
  [ig4] Set STOP condition and flush TX/RX FIFOs on error
  
  if controller has not it done for us yet.
  
  Reset controller when transfer abort is failed.

Modified:
  head/sys/dev/ichiic/ig4_iic.c
  head/sys/dev/ichiic/ig4_reg.h

Modified: head/sys/dev/ichiic/ig4_iic.c
==============================================================================
--- head/sys/dev/ichiic/ig4_iic.c       Sun Nov  3 21:07:12 2019        
(r354314)
+++ head/sys/dev/ichiic/ig4_iic.c       Sun Nov  3 21:08:26 2019        
(r354315)
@@ -117,6 +117,7 @@ static const struct ig4_hw ig4iic_hw[] = {
        },
 };
 
+static int ig4iic_set_config(ig4iic_softc_t *sc, bool reset);
 static void ig4iic_intr(void *cookie);
 static void ig4iic_dump(ig4iic_softc_t *sc);
 
@@ -272,7 +273,7 @@ wait_intr(ig4iic_softc_t *sc, uint32_t intr)
                 * reset the timeout if we see a change in the transmit
                 * FIFO level as progress is being made.
                 */
-               if (intr & IG4_INTR_TX_EMPTY) {
+               if (intr & (IG4_INTR_TX_EMPTY | IG4_INTR_STOP_DET)) {
                        v = reg_read(sc, IG4_REG_TXFLR) & IG4_FIFOLVL_MASK;
                        if (txlvl != v) {
                                txlvl = v;
@@ -369,6 +370,34 @@ ig4iic_xfer_start(ig4iic_softc_t *sc, uint16_t slave, 
        return (0);
 }
 
+static bool
+ig4iic_xfer_is_started(ig4iic_softc_t *sc)
+{
+       /*
+        * It requires that no IG4_REG_CLR_INTR or IG4_REG_CLR_START/STOP_DET
+        * register reads is issued after START condition.
+        */
+       return ((reg_read(sc, IG4_REG_RAW_INTR_STAT) &
+           (IG4_INTR_START_DET | IG4_INTR_STOP_DET)) == IG4_INTR_START_DET);
+}
+
+static int
+ig4iic_xfer_abort(ig4iic_softc_t *sc)
+{
+       int error;
+
+       /* Request send of STOP condition and flush of TX FIFO */
+       set_controller(sc, IG4_I2C_ABORT | IG4_I2C_ENABLE);
+       /*
+        * Wait for the TX_ABRT interrupt with ABRTSRC_TRANSFER
+        * bit set in TX_ABRT_SOURCE register.
+        */
+       error = wait_intr(sc, IG4_INTR_STOP_DET);
+       set_controller(sc, IG4_I2C_ENABLE);
+
+       return (error == IIC_ESTATUS ? 0 : error);
+}
+
 /*
  * Amount of unread data before next burst to get better I2C bus utilization.
  * 2 bytes is enough in FAST mode. 8 bytes is better in FAST+ and HIGH modes.
@@ -584,8 +613,27 @@ ig4iic_transfer(device_t dev, struct iic_msg *msgs, ui
                else
                        error = ig4iic_write(sc, msgs[i].buf, msgs[i].len,
                            rpstart, stop);
-               if (error != 0)
+
+               if (error != 0) {
+                       /*
+                        * Send STOP condition if it's not done yet and flush
+                        * both FIFOs. Do a controller soft reset if transfer
+                        * abort is failed.
+                        */
+                       if (ig4iic_xfer_is_started(sc) &&
+                           ig4iic_xfer_abort(sc) != 0) {
+                               device_printf(sc->dev, "Failed to abort "
+                                   "transfer. Do the controller reset.\n");
+                               ig4iic_set_config(sc, true);
+                       } else {
+                               while (reg_read(sc, IG4_REG_I2C_STA) &
+                                   IG4_STATUS_RX_NOTEMPTY)
+                                       reg_read(sc, IG4_REG_DATA_CMD);
+                               reg_read(sc, IG4_REG_TX_ABRT_SOURCE);
+                               reg_read(sc, IG4_REG_CLR_INTR);
+                       }
                        break;
+               }
 
                rpstart = !stop;
        }
@@ -843,7 +891,7 @@ ig4iic_get_config(ig4iic_softc_t *sc)
 }
 
 static int
-ig4iic_set_config(ig4iic_softc_t *sc)
+ig4iic_set_config(ig4iic_softc_t *sc, bool reset)
 {
        uint32_t v;
 
@@ -851,10 +899,16 @@ ig4iic_set_config(ig4iic_softc_t *sc)
        if (sc->version == IG4_SKYLAKE && (v & IG4_RESTORE_REQUIRED) ) {
                reg_write(sc, IG4_REG_DEVIDLE_CTRL, IG4_DEVICE_IDLE | 
IG4_RESTORE_REQUIRED);
                reg_write(sc, IG4_REG_DEVIDLE_CTRL, 0);
+               pause("i2crst", 1);
+               reset = true;
+       }
 
+       if ((sc->version == IG4_HASWELL || sc->version == IG4_ATOM) && reset) {
+               reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_ASSERT_HSW);
+               reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_DEASSERT_HSW);
+       } else if (sc->version == IG4_SKYLAKE && reset) {
                reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_ASSERT_SKL);
                reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_DEASSERT_SKL);
-               DELAY(1000);
        }
 
        if (sc->version == IG4_ATOM)
@@ -922,6 +976,9 @@ ig4iic_set_config(ig4iic_softc_t *sc)
                  IG4_CTL_RESTARTEN |
                  (sc->cfg.bus_speed & IG4_CTL_SPEED_MASK));
 
+       /* Force setting of the target address on the next transfer */
+       sc->slave_valid = 0;
+
        return (0);
 }
 
@@ -938,7 +995,7 @@ ig4iic_attach(ig4iic_softc_t *sc)
 
        ig4iic_get_config(sc);
 
-       error = ig4iic_set_config(sc);
+       error = ig4iic_set_config(sc, false);
        if (error)
                goto done;
 
@@ -949,19 +1006,6 @@ ig4iic_attach(ig4iic_softc_t *sc)
                goto done;
        }
 
-#if 0
-       /*
-        * Don't do this, it blows up the PCI config
-        */
-       if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) {
-               reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_ASSERT_HSW);
-               reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_DEASSERT_HSW);
-       } else if (sc->version = IG4_SKYLAKE) {
-               reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_ASSERT_SKL);
-               reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_DEASSERT_SKL);
-       }
-#endif
-
        if (set_controller(sc, IG4_I2C_ENABLE)) {
                device_printf(sc->dev, "controller error during attach-2\n");
                error = ENXIO;
@@ -1051,10 +1095,8 @@ int ig4iic_resume(ig4iic_softc_t *sc)
        int error;
 
        sx_xlock(&sc->call_lock);
-       if (ig4iic_set_config(sc))
+       if (ig4iic_set_config(sc, sc->version == IG4_SKYLAKE))
                device_printf(sc->dev, "controller error during resume\n");
-       /* Force setting of the target address on the next transfer */
-       sc->slave_valid = 0;
        sx_xunlock(&sc->call_lock);
 
        error = bus_generic_resume(sc->dev);

Modified: head/sys/dev/ichiic/ig4_reg.h
==============================================================================
--- head/sys/dev/ichiic/ig4_reg.h       Sun Nov  3 21:07:12 2019        
(r354314)
+++ head/sys/dev/ichiic/ig4_reg.h       Sun Nov  3 21:08:26 2019        
(r354315)
@@ -383,7 +383,9 @@
  * I2C_EN      - (RW) I2C Enable Register                      22.2.22
  *
  *     ABORT           Software can abort an I2C transfer by setting this
- *                     bit.  Hardware will clear the bit once the STOP has
+ *                     bit. In response, the controller issues the STOP
+ *                     condition over the I2C bus, followed by TX FIFO flush.
+ *                     Hardware will clear the bit once the STOP has
  *                     been detected.  This bit can only be set while the
  *                     I2C interface is enabled.
  *
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to