Author: wulf
Date: Sun Dec 22 00:46:07 2019
New Revision: 355994
URL: https://svnweb.freebsd.org/changeset/base/355994

Log:
  MFC r354291 - r354322, r354327, r355596
  
  r354291:
  [ig4] Give common name to PCI and ACPI device drivers
  
  r354292:
  [ig4] Handle controller startup errors
  
  Obtained from:        DragonflyBSD (509820b)
  
  r354293:
  [ig4] Only enable interrupts when we want them. Otherwise keep mask at 0.
  
  Obtained from:        DragonflyBSD (d7c8555)
  
  r354294:
  [ig4] Drop driver's internal RX FIFO
  
  r354295:
  [ig4] Do not wait for interrupts in set_controller() routine
  
  r354296:
  [ig4] Reduce scope of io_lock
  
  r354297:
  [ig4] Ignore stray interrupts
  
  r354298:
  [ig4] We actually need to set the Rx threshold register one smaller.
  
  Obtained from:        DragonflyBSD (02f0bf2)
  
  r354299:
  
  [ig4] Stop I2C controller after checking that it's kind of functional.
  
  Obtained from:        DragonfliBSD (0b3eedb)
  
  r354300:
  [ig4] disable controller before initialization of clock counters
  
  r354301:
  [ig4] Add support for polled mode
  
  r354302:
  [ig4] Allow enabling of polled mode from iicbus allocation callback
  
  r354303:
  [ig4] Do not wait until interrupts are enabled at attach stage
  
  r354304:
  [cyapa] Postpone start of the polling thread until sleep is available
  
  r354305:
  [ig4] dump IG4_REG_COMP_PARAM1 and IG4_REG_COMP_VER registers unconditionally
  
  r354306:
  [ig4] Set clock registers based on controller model
  
  r354307:
  [ig4] Implement burst mode for data reads
  
  r354308:
  [ig4] Add suspend/resume support
  
  PR:           238037
  
  r354309:
  [ig4] Remove dead code inherited from DragonflyBSD
  
  r354310:
  [ig4] Rewrite ig4iic_write routine to use TX_EMPTY status flag
  
  r354311:
  [ig4] Convert last remaining usage of TX_NOTFULL status to TX_EMPTY
  
  r354312:
  [ig4] Use interrupts for waiting for empty TX FIFO
  
  r354313:
  [ig4] Convert polling loop from status-based to interrupt-based
  
  r354314:
  [ig4] Improve error detection
  
  r354315:
  [ig4] Set STOP condition and flush TX/RX FIFOs on error
  
  r354316:
  [ig4] On SkyLake controllers issue reset on attach unconditionally.
  
  r354317:
  [ig4] wait for bus stop condition after stop command issued
  
  r354318:
  [ig4] Minor improvement of write pipelining
  
  r354319:
  [ig4] Add generic resource methods to bus interface
  
  r354320:
  [ig4] Add support for CannonLake controllers
  
  PR:           240485
  Submitted by: Neel Chauhan <[email protected]>
  
  r354321:
  [ig4] Enable additional registers support on Appolo Lake controllers
  
  r354322:
  [ig4] Convert ithread interrupt handler to filter based one.
  
  r354327:
  [ig4] Try to workaround MIPS namespace pollution issue
  
  r355596:
  [ig4] Remove unused methods from bus interface
  
  Suggested by: jhb
  
  Reviewed by:          imp (previous version)
  Differential Revision:        https://reviews.freebsd.org/D22016

Modified:
  stable/12/sys/dev/chromebook_platform/chromebook_platform.c
  stable/12/sys/dev/cyapa/cyapa.c
  stable/12/sys/dev/ichiic/ig4_acpi.c
  stable/12/sys/dev/ichiic/ig4_iic.c
  stable/12/sys/dev/ichiic/ig4_pci.c
  stable/12/sys/dev/ichiic/ig4_reg.h
  stable/12/sys/dev/ichiic/ig4_var.h
  stable/12/sys/dev/iicbus/iicbus.c
Directory Properties:
  stable/12/   (props changed)

Modified: stable/12/sys/dev/chromebook_platform/chromebook_platform.c
==============================================================================
--- stable/12/sys/dev/chromebook_platform/chromebook_platform.c Sun Dec 22 
00:36:22 2019        (r355993)
+++ stable/12/sys/dev/chromebook_platform/chromebook_platform.c Sun Dec 22 
00:46:07 2019        (r355994)
@@ -69,7 +69,7 @@ chromebook_i2c_identify(driver_t *driver, device_t bus
         * See 
http://lxr.free-electrons.com/source/drivers/platform/chrome/chromeos_laptop.c
         */
        controller = device_get_parent(bus);
-       if (strcmp(device_get_name(controller), "ig4iic_pci") != 0)
+       if (strcmp(device_get_name(controller), "ig4iic") != 0)
                return;
 
        for (i = 0; i < nitems(slaves); i++) {

Modified: stable/12/sys/dev/cyapa/cyapa.c
==============================================================================
--- stable/12/sys/dev/cyapa/cyapa.c     Sun Dec 22 00:36:22 2019        
(r355993)
+++ stable/12/sys/dev/cyapa/cyapa.c     Sun Dec 22 00:46:07 2019        
(r355994)
@@ -152,6 +152,7 @@ struct cyapa_softc {
        struct cdev *devnode;
        struct selinfo selinfo;
        struct mtx mutex;
+       struct intr_config_hook intr_hook;
 
        int     cap_resx;
        int     cap_resy;
@@ -419,6 +420,27 @@ done:
        return (error);
 }
 
+/*
+ * Start the polling thread
+ */
+static void
+cyapa_start(void *xdev)
+{
+       struct cyapa_softc *sc;
+       device_t dev = xdev;
+
+       sc = device_get_softc(dev);
+
+       config_intrhook_disestablish(&sc->intr_hook);
+
+       /* Setup input event tracking */
+       cyapa_set_power_mode(sc, CMD_POWER_MODE_IDLE);
+
+       /* Start the polling thread */
+       kthread_add(cyapa_poll_thread, sc, NULL, NULL,
+           0, 0, "cyapa-poll");
+}
+
 static int cyapa_probe(device_t);
 static int cyapa_attach(device_t);
 static int cyapa_detach(device_t);
@@ -536,12 +558,14 @@ cyapa_attach(device_t dev)
        sc->mode.level = 0;
        sc->mode.packetsize = MOUSE_PS2_PACKETSIZE;
 
-       /* Setup input event tracking */
-       cyapa_set_power_mode(sc, CMD_POWER_MODE_IDLE);
+       sc->intr_hook.ich_func = cyapa_start;
+       sc->intr_hook.ich_arg = sc->dev;
 
-       /* Start the polling thread */
-        kthread_add(cyapa_poll_thread, sc, NULL, NULL,
-           0, 0, "cyapa-poll");
+       /* Postpone start of the polling thread until sleep is available */
+       if (config_intrhook_establish(&sc->intr_hook) != 0) {
+               mtx_destroy(&sc->mutex);
+               return (ENOMEM);
+       }
 
        sc->devnode = make_dev(&cyapa_cdevsw, unit,
            UID_ROOT, GID_WHEEL, 0600, "cyapa%d", unit);

Modified: stable/12/sys/dev/ichiic/ig4_acpi.c
==============================================================================
--- stable/12/sys/dev/ichiic/ig4_acpi.c Sun Dec 22 00:36:22 2019        
(r355993)
+++ stable/12/sys/dev/ichiic/ig4_acpi.c Sun Dec 22 00:46:07 2019        
(r355994)
@@ -69,20 +69,15 @@ static int
 ig4iic_acpi_probe(device_t dev)
 {
        ig4iic_softc_t *sc;
-       char *hid;
 
        sc = device_get_softc(dev);
 
        if (acpi_disabled("ig4iic"))
                return (ENXIO);
 
-       hid = ACPI_ID_PROBE(device_get_parent(dev), dev, ig4iic_ids);
-       if (hid == NULL)
+       if (ACPI_ID_PROBE(device_get_parent(dev), dev, ig4iic_ids) == NULL)
                return (ENXIO);
 
-       if (strcmp("AMDI0010", hid) == 0)
-               sc->access_intr_mask = 1;
-
        device_set_desc(dev, "Designware I2C Controller");
        return (0);
 }
@@ -150,30 +145,52 @@ ig4iic_acpi_detach(device_t dev)
        return (0);
 }
 
+static int
+ig4iic_acpi_suspend(device_t dev)
+{
+       ig4iic_softc_t *sc = device_get_softc(dev);
+
+       return (ig4iic_suspend(sc));
+}
+
+static int
+ig4iic_acpi_resume(device_t dev)
+{
+       ig4iic_softc_t *sc  = device_get_softc(dev);
+
+       return (ig4iic_resume(sc));
+}
+
 static device_method_t ig4iic_acpi_methods[] = {
        /* Device interface */
        DEVMETHOD(device_probe, ig4iic_acpi_probe),
        DEVMETHOD(device_attach, ig4iic_acpi_attach),
        DEVMETHOD(device_detach, ig4iic_acpi_detach),
+       DEVMETHOD(device_suspend, ig4iic_acpi_suspend),
+       DEVMETHOD(device_resume, ig4iic_acpi_resume),
 
+       /* Bus interface */
+       DEVMETHOD(bus_setup_intr, bus_generic_setup_intr),
+       DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr),
+       DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource),
+       DEVMETHOD(bus_release_resource, bus_generic_release_resource),
+       DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
+       DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
+       DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource),
+
        /* iicbus interface */
        DEVMETHOD(iicbus_transfer, ig4iic_transfer),
        DEVMETHOD(iicbus_reset, ig4iic_reset),
-       DEVMETHOD(iicbus_callback, iicbus_null_callback),
+       DEVMETHOD(iicbus_callback, ig4iic_callback),
 
        DEVMETHOD_END
 };
 
 static driver_t ig4iic_acpi_driver = {
-       "ig4iic_acpi",
+       "ig4iic",
        ig4iic_acpi_methods,
        sizeof(struct ig4iic_softc),
 };
 
-static devclass_t ig4iic_acpi_devclass;
-DRIVER_MODULE(ig4iic_acpi, acpi, ig4iic_acpi_driver, ig4iic_acpi_devclass, 0, 
0);
-
-MODULE_DEPEND(ig4iic_acpi, acpi, 1, 1, 1);
-MODULE_DEPEND(ig4iic_acpi, pci, 1, 1, 1);
-MODULE_DEPEND(ig4iic_acpi, iicbus, IICBUS_MINVER, IICBUS_PREFVER, 
IICBUS_MAXVER);
-MODULE_VERSION(ig4iic_acpi, 1);
+DRIVER_MODULE(ig4iic, acpi, ig4iic_acpi_driver, ig4iic_devclass, 0, 0);
+MODULE_DEPEND(ig4iic, acpi, 1, 1, 1);

Modified: stable/12/sys/dev/ichiic/ig4_iic.c
==============================================================================
--- stable/12/sys/dev/ichiic/ig4_iic.c  Sun Dec 22 00:36:22 2019        
(r355993)
+++ stable/12/sys/dev/ichiic/ig4_iic.c  Sun Dec 22 00:46:07 2019        
(r355994)
@@ -43,13 +43,17 @@ __FBSDID("$FreeBSD$");
  * See ig4_var.h for locking semantics.
  */
 
+#include "opt_acpi.h"
+
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/kernel.h>
 #include <sys/module.h>
 #include <sys/errno.h>
+#include <sys/kdb.h>
 #include <sys/lock.h>
 #include <sys/mutex.h>
+#include <sys/proc.h>
 #include <sys/sx.h>
 #include <sys/syslog.h>
 #include <sys/bus.h>
@@ -58,20 +62,67 @@ __FBSDID("$FreeBSD$");
 #include <machine/bus.h>
 #include <sys/rman.h>
 
-#include <dev/pci/pcivar.h>
-#include <dev/pci/pcireg.h>
+#ifdef DEV_ACPI
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+#endif
+
 #include <dev/iicbus/iicbus.h>
 #include <dev/iicbus/iiconf.h>
 
 #include <dev/ichiic/ig4_reg.h>
 #include <dev/ichiic/ig4_var.h>
 
-#define TRANS_NORMAL   1
-#define TRANS_PCALL    2
-#define TRANS_BLOCK    3
+#define DO_POLL(sc)    (cold || kdb_active || SCHEDULER_STOPPED() || sc->poll)
 
-static void ig4iic_start(void *xdev);
-static void ig4iic_intr(void *cookie);
+/*
+ * tLOW, tHIGH periods of the SCL clock and maximal falling time of both
+ * lines are taken from I2C specifications.
+ */
+#define        IG4_SPEED_STD_THIGH     4000    /* nsec */
+#define        IG4_SPEED_STD_TLOW      4700    /* nsec */
+#define        IG4_SPEED_STD_TF_MAX    300     /* nsec */
+#define        IG4_SPEED_FAST_THIGH    600     /* nsec */
+#define        IG4_SPEED_FAST_TLOW     1300    /* nsec */
+#define        IG4_SPEED_FAST_TF_MAX   300     /* nsec */
+
+/*
+ * Ig4 hardware parameters except Haswell are taken from intel_lpss driver
+ */
+static const struct ig4_hw ig4iic_hw[] = {
+       [IG4_HASWELL] = {
+               .ic_clock_rate = 100,   /* MHz */
+               .sda_hold_time = 90,    /* nsec */
+               .txfifo_depth = 32,
+               .rxfifo_depth = 32,
+       },
+       [IG4_ATOM] = {
+               .ic_clock_rate = 100,
+               .sda_fall_time = 280,
+               .scl_fall_time = 240,
+               .sda_hold_time = 60,
+               .txfifo_depth = 32,
+               .rxfifo_depth = 32,
+       },
+       [IG4_SKYLAKE] = {
+               .ic_clock_rate = 120,
+               .sda_hold_time = 230,
+       },
+       [IG4_APL] = {
+               .ic_clock_rate = 133,
+               .sda_fall_time = 171,
+               .scl_fall_time = 208,
+               .sda_hold_time = 207,
+       },
+       [IG4_CANNONLAKE] = {
+               .ic_clock_rate = 216,
+               .sda_hold_time = 230,
+       },
+};
+
+static int ig4iic_set_config(ig4iic_softc_t *sc, bool reset);
+static driver_filter_t ig4iic_intr;
 static void ig4iic_dump(ig4iic_softc_t *sc);
 
 static int ig4_dump;
@@ -79,6 +130,17 @@ SYSCTL_INT(_debug, OID_AUTO, ig4_dump, CTLFLAG_RW,
           &ig4_dump, 0, "Dump controller registers");
 
 /*
+ * Clock registers initialization control
+ * 0 - Try read clock registers from ACPI and fallback to p.1.
+ * 1 - Calculate values based on controller type (IC clock rate).
+ * 2 - Use values inherited from DragonflyBSD driver (old behavior).
+ * 3 - Keep clock registers intact.
+ */
+static int ig4_timings;
+SYSCTL_INT(_debug, OID_AUTO, ig4_timings, CTLFLAG_RDTUN, &ig4_timings, 0,
+    "Controller timings 0=ACPI, 1=predefined, 2=legacy, 3=do not change");
+
+/*
  * Low-level inline support functions
  */
 static __inline void
@@ -98,6 +160,64 @@ reg_read(ig4iic_softc_t *sc, uint32_t reg)
        return (value);
 }
 
+static void
+ig4iic_set_intr_mask(ig4iic_softc_t *sc, uint32_t val)
+{
+       if (sc->intr_mask != val) {
+               reg_write(sc, IG4_REG_INTR_MASK, val);
+               sc->intr_mask = val;
+       }
+}
+
+static int
+intrstat2iic(ig4iic_softc_t *sc, uint32_t val)
+{
+       uint32_t src;
+
+       if (val & IG4_INTR_RX_UNDER)
+               reg_read(sc, IG4_REG_CLR_RX_UNDER);
+       if (val & IG4_INTR_RX_OVER)
+               reg_read(sc, IG4_REG_CLR_RX_OVER);
+       if (val & IG4_INTR_TX_OVER)
+               reg_read(sc, IG4_REG_CLR_TX_OVER);
+
+       if (val & IG4_INTR_TX_ABRT) {
+               src = reg_read(sc, IG4_REG_TX_ABRT_SOURCE);
+               reg_read(sc, IG4_REG_CLR_TX_ABORT);
+               /* User-requested abort. Not really a error */
+               if (src & IG4_ABRTSRC_TRANSFER)
+                       return (IIC_ESTATUS);
+               /* Master has lost arbitration */
+               if (src & IG4_ABRTSRC_ARBLOST)
+                       return (IIC_EBUSBSY);
+               /* Did not receive an acknowledge from the remote slave */
+               if (src & (IG4_ABRTSRC_TXNOACK_ADDR7 |
+                          IG4_ABRTSRC_TXNOACK_ADDR10_1 |
+                          IG4_ABRTSRC_TXNOACK_ADDR10_2 |
+                          IG4_ABRTSRC_TXNOACK_DATA |
+                          IG4_ABRTSRC_GENCALL_NOACK))
+                       return (IIC_ENOACK);
+               /* Programming errors */
+               if (src & (IG4_ABRTSRC_GENCALL_READ |
+                          IG4_ABRTSRC_NORESTART_START |
+                          IG4_ABRTSRC_NORESTART_10))
+                       return (IIC_ENOTSUPP);
+               /* Other errors */
+               if (src & IG4_ABRTSRC_ACKED_START)
+                       return (IIC_EBUSERR);
+       }
+       /*
+        * TX_OVER, RX_OVER and RX_UNDER are caused by wrong RX/TX FIFO depth
+        * detection or driver's read/write pipelining errors.
+        */
+       if (val & (IG4_INTR_TX_OVER | IG4_INTR_RX_OVER))
+               return (IIC_EOVERFLOW);
+       if (val & IG4_INTR_RX_UNDER)
+               return (IIC_EUNDERFLOW);
+
+       return (IIC_NOERR);
+}
+
 /*
  * Enable or disable the controller and wait for the controller to acknowledge
  * the state change.
@@ -113,12 +233,9 @@ set_controller(ig4iic_softc_t *sc, uint32_t ctl)
         * When the controller is enabled, interrupt on STOP detect
         * or receive character ready and clear pending interrupts.
         */
-       if (ctl & IG4_I2C_ENABLE) {
-               reg_write(sc, IG4_REG_INTR_MASK, IG4_INTR_STOP_DET |
-                                                IG4_INTR_RX_FULL);
+       ig4iic_set_intr_mask(sc, 0);
+       if (ctl & IG4_I2C_ENABLE)
                reg_read(sc, IG4_REG_CLR_INTR);
-       } else
-               reg_write(sc, IG4_REG_INTR_MASK, 0);
 
        reg_write(sc, IG4_REG_I2C_EN, ctl);
        error = IIC_ETIMEOUT;
@@ -129,19 +246,16 @@ set_controller(ig4iic_softc_t *sc, uint32_t ctl)
                        error = 0;
                        break;
                }
-               if (cold)
-                       DELAY(1000);
-               else
-                       mtx_sleep(sc, &sc->io_lock, 0, "i2cslv", 1);
+               pause("i2cslv", 1);
        }
        return (error);
 }
 
 /*
- * Wait up to 25ms for the requested status using a 25uS polling loop.
+ * Wait up to 25ms for the requested interrupt using a 25uS polling loop.
  */
 static int
-wait_status(ig4iic_softc_t *sc, uint32_t status)
+wait_intr(ig4iic_softc_t *sc, uint32_t intr)
 {
        uint32_t v;
        int error;
@@ -149,35 +263,21 @@ wait_status(ig4iic_softc_t *sc, uint32_t status)
        u_int count_us = 0;
        u_int limit_us = 25000; /* 25ms */
 
-       error = IIC_ETIMEOUT;
-
        for (;;) {
                /*
                 * Check requested status
                 */
-               v = reg_read(sc, IG4_REG_I2C_STA);
-               if (v & status) {
-                       error = 0;
+               v = reg_read(sc, IG4_REG_RAW_INTR_STAT);
+               error = intrstat2iic(sc, v & IG4_INTR_ERR_MASK);
+               if (error || (v & intr))
                        break;
-               }
 
                /*
-                * When waiting for receive data break-out if the interrupt
-                * loaded data into the FIFO.
-                */
-               if (status & IG4_STATUS_RX_NOTEMPTY) {
-                       if (sc->rpos != sc->rnext) {
-                               error = 0;
-                               break;
-                       }
-               }
-
-               /*
                 * When waiting for the transmit FIFO to become empty,
                 * reset the timeout if we see a change in the transmit
                 * FIFO level as progress is being made.
                 */
-               if (status & IG4_STATUS_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;
@@ -188,16 +288,21 @@ wait_status(ig4iic_softc_t *sc, uint32_t status)
                /*
                 * Stop if we've run out of time.
                 */
-               if (count_us >= limit_us)
+               if (count_us >= limit_us) {
+                       error = IIC_ETIMEOUT;
                        break;
+               }
 
                /*
-                * When waiting for receive data let the interrupt do its
-                * work, otherwise poll with the lock held.
+                * When polling is not requested let the interrupt do its work.
                 */
-               if (status & IG4_STATUS_RX_NOTEMPTY) {
-                       mtx_sleep(sc, &sc->io_lock, 0, "i2cwait",
+               if (!DO_POLL(sc)) {
+                       mtx_lock_spin(&sc->io_lock);
+                       ig4iic_set_intr_mask(sc, intr | IG4_INTR_ERR_MASK);
+                       msleep_spin(sc, &sc->io_lock, "i2cwait",
                                  (hz + 99) / 100); /* sleep up to 10ms */
+                       ig4iic_set_intr_mask(sc, 0);
+                       mtx_unlock_spin(&sc->io_lock);
                        count_us += 10000;
                } else {
                        DELAY(25);
@@ -209,25 +314,6 @@ wait_status(ig4iic_softc_t *sc, uint32_t status)
 }
 
 /*
- * Read I2C data.  The data might have already been read by
- * the interrupt code, otherwise it is sitting in the data
- * register.
- */
-static uint8_t
-data_read(ig4iic_softc_t *sc)
-{
-       uint8_t c;
-
-       if (sc->rpos == sc->rnext) {
-               c = (uint8_t)reg_read(sc, IG4_REG_DATA_CMD);
-       } else {
-               c = sc->rbuf[sc->rpos & IG4_RBUFMASK];
-               ++sc->rpos;
-       }
-       return (c);
-}
-
-/*
  * Set the slave address.  The controller must be disabled when
  * changing the address.
  *
@@ -250,22 +336,8 @@ set_slave_addr(ig4iic_softc_t *sc, uint8_t slave)
 
        /*
         * Wait for TXFIFO to drain before disabling the controller.
-        *
-        * If a write message has not been completed it's really a
-        * programming error, but for now in that case issue an extra
-        * byte + STOP.
-        *
-        * If a read message has not been completed it's also a programming
-        * error, for now just ignore it.
         */
-       wait_status(sc, IG4_STATUS_TX_NOTFULL);
-       if (sc->write_started) {
-               reg_write(sc, IG4_REG_DATA_CMD, IG4_DATA_STOP);
-               sc->write_started = 0;
-       }
-       if (sc->read_started)
-               sc->read_started = 0;
-       wait_status(sc, IG4_STATUS_TX_EMPTY);
+       wait_intr(sc, IG4_INTR_TX_EMPTY);
 
        set_controller(sc, 0);
        ctl = reg_read(sc, IG4_REG_CTL);
@@ -288,48 +360,110 @@ set_slave_addr(ig4iic_softc_t *sc, uint8_t slave)
  *                             IICBUS API FUNCTIONS
  */
 static int
-ig4iic_xfer_start(ig4iic_softc_t *sc, uint16_t slave)
+ig4iic_xfer_start(ig4iic_softc_t *sc, uint16_t slave, bool repeated_start)
 {
        set_slave_addr(sc, slave >> 1);
+
+       if (!repeated_start) {
+               /*
+                * Clear any previous TX/RX FIFOs overflow/underflow bits
+                * and I2C bus STOP condition.
+                */
+               reg_read(sc, IG4_REG_CLR_INTR);
+       }
+
        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.
+ * Intel-recommended value is 16 for DMA transfers with 64-byte depth FIFOs.
+ */
+#define        IG4_FIFO_LOWAT  2
+
+static int
 ig4iic_read(ig4iic_softc_t *sc, uint8_t *buf, uint16_t len,
     bool repeated_start, bool stop)
 {
        uint32_t cmd;
-       uint16_t i;
+       int requested = 0;
+       int received = 0;
+       int burst, target, lowat = 0;
        int error;
 
        if (len == 0)
                return (0);
 
-       cmd = IG4_DATA_COMMAND_RD;
-       cmd |= repeated_start ? IG4_DATA_RESTART : 0;
-       cmd |= stop && len == 1 ? IG4_DATA_STOP : 0;
-
-       /* Issue request for the first byte (could be last as well). */
-       reg_write(sc, IG4_REG_DATA_CMD, cmd);
-
-       for (i = 0; i < len; i++) {
-               /*
-                * Maintain a pipeline by queueing the allowance for the next
-                * read before waiting for the current read.
-                */
-               cmd = IG4_DATA_COMMAND_RD;
-               if (i < len - 1) {
+       while (received < len) {
+               burst = sc->cfg.txfifo_depth -
+                   (reg_read(sc, IG4_REG_TXFLR) & IG4_FIFOLVL_MASK);
+               if (burst <= 0) {
+                       error = wait_intr(sc, IG4_INTR_TX_EMPTY);
+                       if (error)
+                               break;
+                       burst = sc->cfg.txfifo_depth;
+               }
+               /* Ensure we have enough free space in RXFIFO */
+               burst = MIN(burst, sc->cfg.rxfifo_depth - lowat);
+               target = MIN(requested + burst, (int)len);
+               while (requested < target) {
                        cmd = IG4_DATA_COMMAND_RD;
-                       cmd |= stop && i == len - 2 ? IG4_DATA_STOP : 0;
+                       if (repeated_start && requested == 0)
+                               cmd |= IG4_DATA_RESTART;
+                       if (stop && requested == len - 1)
+                               cmd |= IG4_DATA_STOP;
                        reg_write(sc, IG4_REG_DATA_CMD, cmd);
+                       requested++;
                }
-               error = wait_status(sc, IG4_STATUS_RX_NOTEMPTY);
-               if (error)
-                       break;
-               buf[i] = data_read(sc);
+               /* Leave some data queued to maintain the hardware pipeline */
+               lowat = 0;
+               if (requested != len && requested - received > IG4_FIFO_LOWAT)
+                       lowat = IG4_FIFO_LOWAT;
+               /* After TXFLR fills up, clear it by reading available data */
+               while (received < requested - lowat) {
+                       burst = MIN((int)len - received,
+                           reg_read(sc, IG4_REG_RXFLR) & IG4_FIFOLVL_MASK);
+                       if (burst > 0) {
+                               while (burst--)
+                                       buf[received++] = 0xFF &
+                                           reg_read(sc, IG4_REG_DATA_CMD);
+                       } else {
+                               error = wait_intr(sc, IG4_INTR_RX_FULL);
+                               if (error)
+                                       goto out;
+                       }
+               }
        }
-
-       (void)reg_read(sc, IG4_REG_TX_ABRT_SOURCE);
+out:
        return (error);
 }
 
@@ -338,24 +472,41 @@ ig4iic_write(ig4iic_softc_t *sc, uint8_t *buf, uint16_
     bool repeated_start, bool stop)
 {
        uint32_t cmd;
-       uint16_t i;
+       int sent = 0;
+       int burst, target;
        int error;
+       bool lowat_set = false;
 
        if (len == 0)
                return (0);
 
-       cmd = repeated_start ? IG4_DATA_RESTART : 0;
-       for (i = 0; i < len; i++) {
-               error = wait_status(sc, IG4_STATUS_TX_NOTFULL);
-               if (error)
-                       break;
-               cmd |= buf[i];
-               cmd |= stop && i == len - 1 ? IG4_DATA_STOP : 0;
-               reg_write(sc, IG4_REG_DATA_CMD, cmd);
-               cmd = 0;
+       while (sent < len) {
+               burst = sc->cfg.txfifo_depth -
+                   (reg_read(sc, IG4_REG_TXFLR) & IG4_FIFOLVL_MASK);
+               target = MIN(sent + burst, (int)len);
+               /* Leave some data queued to maintain the hardware pipeline */
+               if (!lowat_set && target != len) {
+                       lowat_set = true;
+                       reg_write(sc, IG4_REG_TX_TL, IG4_FIFO_LOWAT);
+               }
+               while(sent < target) {
+                       cmd = buf[sent];
+                       if (repeated_start && sent == 0)
+                               cmd |= IG4_DATA_RESTART;
+                       if (stop && sent == len - 1)
+                               cmd |= IG4_DATA_STOP;
+                       reg_write(sc, IG4_REG_DATA_CMD, cmd);
+                       sent++;
+               }
+               if (sent < len) {
+                       error = wait_intr(sc, IG4_INTR_TX_EMPTY);
+                       if (error)
+                               break;
+               }
        }
+       if (lowat_set)
+               reg_write(sc, IG4_REG_TX_TL, 0);
 
-       (void)reg_read(sc, IG4_REG_TX_ABRT_SOURCE);
        return (error);
 }
 
@@ -369,6 +520,7 @@ ig4iic_transfer(device_t dev, struct iic_msg *msgs, ui
        int unit;
        bool rpstart;
        bool stop;
+       bool allocated;
 
        /*
         * The hardware interface imposes limits on allowed I2C messages.
@@ -429,8 +581,10 @@ ig4iic_transfer(device_t dev, struct iic_msg *msgs, ui
                return (IIC_ENOTSUPP);
        }
 
-       sx_xlock(&sc->call_lock);
-       mtx_lock(&sc->io_lock);
+       /* Check if device is already allocated with iicbus_request_bus() */
+       allocated = sx_xlocked(&sc->call_lock) != 0;
+       if (!allocated)
+               sx_xlock(&sc->call_lock);
 
        /* Debugging - dump registers. */
        if (ig4_dump) {
@@ -447,21 +601,11 @@ ig4iic_transfer(device_t dev, struct iic_msg *msgs, ui
         */
        reg_read(sc, IG4_REG_CLR_TX_ABORT);
 
-       /*
-        * Clean out any previously received data.
-        */
-       if (sc->rpos != sc->rnext && bootverbose) {
-               device_printf(sc->dev, "discarding %d bytes of spurious data\n",
-                   sc->rnext - sc->rpos);
-       }
-       sc->rpos = 0;
-       sc->rnext = 0;
-
        rpstart = false;
        error = 0;
        for (i = 0; i < nmsgs; i++) {
                if ((msgs[i].flags & IIC_M_NOSTART) == 0) {
-                       error = ig4iic_xfer_start(sc, msgs[i].slave);
+                       error = ig4iic_xfer_start(sc, msgs[i].slave, rpstart);
                } else {
                        if (!sc->slave_valid ||
                            (msgs[i].slave >> 1) != sc->last_slave) {
@@ -482,14 +626,40 @@ 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)
+
+               /* Wait for error or stop condition occurred on the I2C bus */
+               if (stop && error == 0) {
+                       error = wait_intr(sc, IG4_INTR_STOP_DET);
+                       if (error == 0)
+                               reg_read(sc, IG4_REG_CLR_INTR);
+               }
+
+               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;
        }
 
-       mtx_unlock(&sc->io_lock);
-       sx_unlock(&sc->call_lock);
+       if (!allocated)
+               sx_unlock(&sc->call_lock);
        return (error);
 }
 
@@ -497,9 +667,11 @@ int
 ig4iic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
 {
        ig4iic_softc_t *sc = device_get_softc(dev);
+       bool allocated;
 
-       sx_xlock(&sc->call_lock);
-       mtx_lock(&sc->io_lock);
+       allocated = sx_xlocked(&sc->call_lock) != 0;
+       if (!allocated)
+               sx_xlock(&sc->call_lock);
 
        /* TODO handle speed configuration? */
        if (oldaddr != NULL)
@@ -508,31 +680,255 @@ ig4iic_reset(device_t dev, u_char speed, u_char addr, 
        if (addr == IIC_UNKNOWN)
                sc->slave_valid = false;
 
-       mtx_unlock(&sc->io_lock);
-       sx_unlock(&sc->call_lock);
+       if (!allocated)
+               sx_unlock(&sc->call_lock);
        return (0);
 }
 
+int
+ig4iic_callback(device_t dev, int index, caddr_t data)
+{
+       ig4iic_softc_t *sc = device_get_softc(dev);
+       int error = 0;
+       int how;
+
+       switch (index) {
+       case IIC_REQUEST_BUS:
+               /* force polling if ig4iic is requested with IIC_DONTWAIT */
+               how = *(int *)data;
+               if ((how & IIC_WAIT) == 0) {
+                       if (sx_try_xlock(&sc->call_lock) == 0)
+                               error = IIC_EBUSBSY;
+                       else
+                               sc->poll = true;
+               } else
+                       sx_xlock(&sc->call_lock);
+               break;
+
+       case IIC_RELEASE_BUS:
+               sc->poll = false;
+               sx_unlock(&sc->call_lock);
+               break;
+
+       default:
+               error = errno2iic(EINVAL);
+       }
+
+       return (error);
+}
+
 /*
- * Called from ig4iic_pci_attach/detach()
+ * Clock register values can be calculated with following rough equations:
+ * SCL_HCNT = ceil(IC clock rate * tHIGH)
+ * SCL_LCNT = ceil(IC clock rate * tLOW)
+ * SDA_HOLD = ceil(IC clock rate * SDA hold time)
+ * Precise equations take signal's falling, rising and spike suppression
+ * times in to account. They can be found in Synopsys or Intel documentation.
+ *
+ * Here we snarf formulas and defaults from Linux driver to be able to use
+ * timing values provided by Intel LPSS driver "as is".
  */
-int
-ig4iic_attach(ig4iic_softc_t *sc)
+static int
+ig4iic_clk_params(const struct ig4_hw *hw, int speed,
+    uint16_t *scl_hcnt, uint16_t *scl_lcnt, uint16_t *sda_hold)
 {
-       int error;
+       uint32_t thigh, tlow, tf_max;   /* nsec */
+       uint32_t sda_fall_time;         /* nsec */
+        uint32_t scl_fall_time;                /* nsec */
+
+       switch (speed) {
+       case IG4_CTL_SPEED_STD:
+               thigh = IG4_SPEED_STD_THIGH;
+               tlow = IG4_SPEED_STD_TLOW;
+               tf_max = IG4_SPEED_STD_TF_MAX;
+               break;
+
+       case IG4_CTL_SPEED_FAST:
+               thigh = IG4_SPEED_FAST_THIGH;
+               tlow = IG4_SPEED_FAST_TLOW;
+               tf_max = IG4_SPEED_FAST_TF_MAX;
+               break;
+
+       default:
+               return (EINVAL);
+       }
+
+       /* Use slowest falling time defaults to be on the safe side */
+       sda_fall_time = hw->sda_fall_time == 0 ? tf_max : hw->sda_fall_time;
+       *scl_hcnt = (uint16_t)
+           ((hw->ic_clock_rate * (thigh + sda_fall_time) + 500) / 1000 - 3);
+
+       scl_fall_time = hw->scl_fall_time == 0 ? tf_max : hw->scl_fall_time;
+       *scl_lcnt = (uint16_t)
+           ((hw->ic_clock_rate * (tlow + scl_fall_time) + 500) / 1000 - 1);
+
+       /*
+        * There is no "known good" default value for tHD;DAT so keep SDA_HOLD
+        * intact if sda_hold_time value is not provided.
+        */
+       if (hw->sda_hold_time != 0)
+               *sda_hold = (uint16_t)
+                   ((hw->ic_clock_rate * hw->sda_hold_time + 500) / 1000);
+
+       return (0);
+}
+
+#ifdef DEV_ACPI
+static ACPI_STATUS
+ig4iic_acpi_params(ACPI_HANDLE handle, char *method,
+    uint16_t *scl_hcnt, uint16_t *scl_lcnt, uint16_t *sda_hold)
+{
+       ACPI_BUFFER buf;
+       ACPI_OBJECT *obj, *elems;
+       ACPI_STATUS status;
+
+       buf.Pointer = NULL;
+       buf.Length = ACPI_ALLOCATE_BUFFER;
+
+       status = AcpiEvaluateObject(handle, method, NULL, &buf);
+       if (ACPI_FAILURE(status))
+               return (status);
+
+       status = AE_TYPE;
+       obj = (ACPI_OBJECT *)buf.Pointer;
+       if (obj->Type == ACPI_TYPE_PACKAGE && obj->Package.Count == 3) {
+               elems = obj->Package.Elements;
+               *scl_hcnt = elems[0].Integer.Value & IG4_SCL_CLOCK_MASK;
+               *scl_lcnt = elems[1].Integer.Value & IG4_SCL_CLOCK_MASK;
+               *sda_hold = elems[2].Integer.Value & IG4_SDA_TX_HOLD_MASK;
+               status = AE_OK;
+       }
+
+       AcpiOsFree(obj);
+
+       return (status);
+}
+#endif /* DEV_ACPI */
+
+static void
+ig4iic_get_config(ig4iic_softc_t *sc)
+{
+       const struct ig4_hw *hw;
        uint32_t v;
+#ifdef DEV_ACPI
+       ACPI_HANDLE handle;
+#endif
+       /* Fetch default hardware config from controller */
+       sc->cfg.version = reg_read(sc, IG4_REG_COMP_VER);
+       sc->cfg.bus_speed = reg_read(sc, IG4_REG_CTL) & IG4_CTL_SPEED_MASK;
+       sc->cfg.ss_scl_hcnt =
+           reg_read(sc, IG4_REG_SS_SCL_HCNT) & IG4_SCL_CLOCK_MASK;
+       sc->cfg.ss_scl_lcnt =
+           reg_read(sc, IG4_REG_SS_SCL_LCNT) & IG4_SCL_CLOCK_MASK;
+       sc->cfg.fs_scl_hcnt =
+           reg_read(sc, IG4_REG_FS_SCL_HCNT) & IG4_SCL_CLOCK_MASK;
+       sc->cfg.fs_scl_lcnt =
+           reg_read(sc, IG4_REG_FS_SCL_LCNT) & IG4_SCL_CLOCK_MASK;
+       sc->cfg.ss_sda_hold = sc->cfg.fs_sda_hold =
+           reg_read(sc, IG4_REG_SDA_HOLD) & IG4_SDA_TX_HOLD_MASK;
 
-       mtx_init(&sc->io_lock, "IG4 I/O lock", NULL, MTX_DEF);
-       sx_init(&sc->call_lock, "IG4 call lock");
+       if (sc->cfg.bus_speed != IG4_CTL_SPEED_STD)
+               sc->cfg.bus_speed = IG4_CTL_SPEED_FAST;
 
+       /* REG_COMP_PARAM1 is not documented in latest Intel specs */
+       if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) {
+               v = reg_read(sc, IG4_REG_COMP_PARAM1);
+               if (IG4_PARAM1_TXFIFO_DEPTH(v) != 0)
+                       sc->cfg.txfifo_depth = IG4_PARAM1_TXFIFO_DEPTH(v);
+               if (IG4_PARAM1_RXFIFO_DEPTH(v) != 0)
+                       sc->cfg.rxfifo_depth = IG4_PARAM1_RXFIFO_DEPTH(v);
+       } else {
+               /*
+                * Hardware does not allow FIFO Threshold Levels value to be
+                * set larger than the depth of the buffer. If an attempt is
+                * made to do that, the actual value set will be the maximum
+                * depth of the buffer.
+                */
+               v = reg_read(sc, IG4_REG_TX_TL);
+               reg_write(sc, IG4_REG_TX_TL, v | IG4_FIFO_MASK);
+               sc->cfg.txfifo_depth =
+                   (reg_read(sc, IG4_REG_TX_TL) & IG4_FIFO_MASK) + 1;
+               reg_write(sc, IG4_REG_TX_TL, v);
+               v = reg_read(sc, IG4_REG_RX_TL);
+               reg_write(sc, IG4_REG_RX_TL, v | IG4_FIFO_MASK);
+               sc->cfg.rxfifo_depth =
+                   (reg_read(sc, IG4_REG_RX_TL) & IG4_FIFO_MASK) + 1;
+               reg_write(sc, IG4_REG_RX_TL, v);
+       }
+
+       /* Override hardware config with IC_clock-based counter values */
+       if (ig4_timings < 2 && sc->version < nitems(ig4iic_hw)) {
+               hw = &ig4iic_hw[sc->version];
+               sc->cfg.bus_speed = IG4_CTL_SPEED_FAST;
+               ig4iic_clk_params(hw, IG4_CTL_SPEED_STD, &sc->cfg.ss_scl_hcnt,
+                   &sc->cfg.ss_scl_lcnt, &sc->cfg.ss_sda_hold);
+               ig4iic_clk_params(hw, IG4_CTL_SPEED_FAST, &sc->cfg.fs_scl_hcnt,
+                   &sc->cfg.fs_scl_lcnt, &sc->cfg.fs_sda_hold);
+               if (hw->txfifo_depth != 0)
+                       sc->cfg.txfifo_depth = hw->txfifo_depth;
+               if (hw->rxfifo_depth != 0)
+                       sc->cfg.rxfifo_depth = hw->rxfifo_depth;
+       } else if (ig4_timings == 2) {
+               /*
+                * Timings of original ig4 driver:
+                * Program based on a 25000 Hz clock.  This is a bit of a
+                * hack (obviously).  The defaults are 400 and 470 for standard
+                * and 60 and 130 for fast.  The defaults for standard fail
+                * utterly (presumably cause an abort) because the clock time
+                * is ~18.8ms by default.  This brings it down to ~4ms.
+                */
+               sc->cfg.bus_speed = IG4_CTL_SPEED_STD;
+               sc->cfg.ss_scl_hcnt = sc->cfg.fs_scl_hcnt = 100;
+               sc->cfg.ss_scl_lcnt = sc->cfg.fs_scl_lcnt = 125;
+               if (sc->version == IG4_SKYLAKE)
+                       sc->cfg.ss_sda_hold = sc->cfg.fs_sda_hold = 28;
+       }
+
+#ifdef DEV_ACPI
+       /* Evaluate SSCN and FMCN ACPI methods to fetch timings */
+       if (ig4_timings == 0 && (handle = acpi_get_handle(sc->dev)) != NULL) {
+               ig4iic_acpi_params(handle, "SSCN", &sc->cfg.ss_scl_hcnt,
+                   &sc->cfg.ss_scl_lcnt, &sc->cfg.ss_sda_hold);
+               ig4iic_acpi_params(handle, "FMCN", &sc->cfg.fs_scl_hcnt,
+                   &sc->cfg.fs_scl_lcnt, &sc->cfg.fs_sda_hold);

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
_______________________________________________
[email protected] mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "[email protected]"

Reply via email to