Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 22 Dec 2019 00:46:07 +0000 (UTC)
From:      Vladimir Kondratyev <wulf@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-12@freebsd.org
Subject:   svn commit: r355994 - in stable/12/sys/dev: chromebook_platform cyapa ichiic iicbus
Message-ID:  <201912220046.xBM0k7Lo069223@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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 <neel@neelc.org>
  
  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 ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201912220046.xBM0k7Lo069223>