svn commit: r354306 - head/sys/dev/ichiic

Vladimir Kondratyev wulf at FreeBSD.org
Sun Nov 3 20:58:00 UTC 2019


Author: wulf
Date: Sun Nov  3 20:57:59 2019
New Revision: 354306
URL: https://svnweb.freebsd.org/changeset/base/354306

Log:
  [ig4] Set clock registers based on controller model
  
  IC clock rates are varied between different controller models so we have
  to adjust timing registers in each case individually. Borrow intresting
  constants and formulas from Intel specs, i2c-designware and lpss_intel
  drivers and apply them to FreeBSD supported controller models.
  
  Implement fetching of timing data via ACPI methods execution if available.

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

Modified: head/sys/dev/ichiic/ig4_iic.c
==============================================================================
--- head/sys/dev/ichiic/ig4_iic.c	Sun Nov  3 20:56:53 2019	(r354305)
+++ head/sys/dev/ichiic/ig4_iic.c	Sun Nov  3 20:57:59 2019	(r354306)
@@ -43,6 +43,8 @@ __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>
@@ -60,6 +62,12 @@ __FBSDID("$FreeBSD$");
 #include <machine/bus.h>
 #include <sys/rman.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/pci/pcivar.h>
 #include <dev/pci/pcireg.h>
 #include <dev/iicbus/iicbus.h>
@@ -74,6 +82,43 @@ __FBSDID("$FreeBSD$");
 
 #define DO_POLL(sc)	(cold || kdb_active || SCHEDULER_STOPPED() || sc->poll)
 
+/*
+ * 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 */
+	},
+	[IG4_ATOM] = {
+		.ic_clock_rate = 100,
+		.sda_fall_time = 280,
+		.scl_fall_time = 240,
+		.sda_hold_time = 60,
+	},
+	[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,
+	},
+};
+
 static void ig4iic_intr(void *cookie);
 static void ig4iic_dump(ig4iic_softc_t *sc);
 
@@ -82,6 +127,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
@@ -521,6 +577,166 @@ ig4iic_callback(device_t dev, int index, caddr_t data)
 }
 
 /*
+ * 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".
+ */
+static int
+ig4iic_clk_params(const struct ig4_hw *hw, int speed,
+    uint16_t *scl_hcnt, uint16_t *scl_lcnt, uint16_t *sda_hold)
+{
+	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;
+#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;
+
+	if (sc->cfg.bus_speed != IG4_CTL_SPEED_STD)
+		sc->cfg.bus_speed = IG4_CTL_SPEED_FAST;
+
+	/* 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);
+	} 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);
+	}
+#endif
+
+	if (bootverbose) {
+		device_printf(sc->dev, "Controller parameters:\n");
+		printf("  Speed: %s\n",
+		    sc->cfg.bus_speed == IG4_CTL_SPEED_STD ? "Std" : "Fast");
+		printf("  Regs:  HCNT  :LCNT  :SDAHLD\n");
+		printf("  Std:   0x%04hx:0x%04hx:0x%04hx\n",
+		    sc->cfg.ss_scl_hcnt, sc->cfg.ss_scl_lcnt,
+		    sc->cfg.ss_sda_hold);
+		printf("  Fast:  0x%04hx:0x%04hx:0x%04hx\n",
+		    sc->cfg.fs_scl_hcnt, sc->cfg.fs_scl_lcnt,
+		    sc->cfg.fs_sda_hold);
+	}
+}
+
+/*
  * Called from ig4iic_pci_attach/detach()
  */
 int
@@ -532,6 +748,8 @@ ig4iic_attach(ig4iic_softc_t *sc)
 	mtx_init(&sc->io_lock, "IG4 I/O lock", NULL, MTX_DEF);
 	sx_init(&sc->call_lock, "IG4 call lock");
 
+	ig4iic_get_config(sc);
+
 	v = reg_read(sc, IG4_REG_DEVIDLE_CTRL);
 	if (sc->version == IG4_SKYLAKE && (v & IG4_RESTORE_REQUIRED) ) {
 		reg_write(sc, IG4_REG_DEVIDLE_CTRL, IG4_DEVICE_IDLE | IG4_RESTORE_REQUIRED);
@@ -582,34 +800,17 @@ ig4iic_attach(ig4iic_softc_t *sc)
 		goto done;
 	}
 
-	v = reg_read(sc, IG4_REG_SS_SCL_HCNT);
-	v = reg_read(sc, IG4_REG_SS_SCL_LCNT);
-	v = reg_read(sc, IG4_REG_FS_SCL_HCNT);
-	v = reg_read(sc, IG4_REG_FS_SCL_LCNT);
-	v = reg_read(sc, IG4_REG_SDA_HOLD);
-
-	v = reg_read(sc, IG4_REG_SS_SCL_HCNT);
-	reg_write(sc, IG4_REG_FS_SCL_HCNT, v);
-	v = reg_read(sc, IG4_REG_SS_SCL_LCNT);
-	reg_write(sc, IG4_REG_FS_SCL_LCNT, v);
-
 	reg_read(sc, IG4_REG_CLR_INTR);
 	reg_write(sc, IG4_REG_INTR_MASK, 0);
 	sc->intr_mask = 0;
 
-	/*
-	 * 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 (for now).
-	 */
-	reg_write(sc, IG4_REG_SS_SCL_HCNT, 100);
-	reg_write(sc, IG4_REG_SS_SCL_LCNT, 125);
-	reg_write(sc, IG4_REG_FS_SCL_HCNT, 100);
-	reg_write(sc, IG4_REG_FS_SCL_LCNT, 125);
-	if (sc->version == IG4_SKYLAKE)
-		reg_write(sc, IG4_REG_SDA_HOLD, 28);
+	reg_write(sc, IG4_REG_SS_SCL_HCNT, sc->cfg.ss_scl_hcnt);
+	reg_write(sc, IG4_REG_SS_SCL_LCNT, sc->cfg.ss_scl_lcnt);
+	reg_write(sc, IG4_REG_FS_SCL_HCNT, sc->cfg.fs_scl_hcnt);
+	reg_write(sc, IG4_REG_FS_SCL_LCNT, sc->cfg.fs_scl_lcnt);
+	reg_write(sc, IG4_REG_SDA_HOLD,
+	    (sc->cfg.bus_speed  & IG4_CTL_SPEED_MASK) == IG4_CTL_SPEED_STD ?
+	      sc->cfg.ss_sda_hold : sc->cfg.fs_sda_hold);
 
 	/*
 	 * Use a threshold of 1 so we get interrupted on each character,
@@ -624,7 +825,7 @@ ig4iic_attach(ig4iic_softc_t *sc)
 		  IG4_CTL_MASTER |
 		  IG4_CTL_SLAVE_DISABLE |
 		  IG4_CTL_RESTARTEN |
-		  IG4_CTL_SPEED_STD);
+		  (sc->cfg.bus_speed & IG4_CTL_SPEED_MASK));
 
 	sc->iicbus = device_add_child(sc->dev, "iicbus", -1);
 	if (sc->iicbus == NULL) {

Modified: head/sys/dev/ichiic/ig4_reg.h
==============================================================================
--- head/sys/dev/ichiic/ig4_reg.h	Sun Nov  3 20:56:53 2019	(r354305)
+++ head/sys/dev/ichiic/ig4_reg.h	Sun Nov  3 20:57:59 2019	(r354306)
@@ -154,9 +154,12 @@
 #define IG4_CTL_SLAVE_DISABLE	0x0040	/* snarfed from linux */
 #define IG4_CTL_RESTARTEN	0x0020	/* Allow Restart when master */
 #define IG4_CTL_10BIT		0x0010	/* ctlr accepts 10-bit addresses */
+#define IG4_CTL_SPEED_MASK	0x0006	/* speed at which the I2C operates */
+#define IG4_CTL_MASTER		0x0001	/* snarfed from linux */
+
+#define IG4_CTL_SPEED_HIGH	0x0006	/* snarfed from linux */
 #define IG4_CTL_SPEED_FAST	0x0004	/* snarfed from linux */
 #define IG4_CTL_SPEED_STD	0x0002	/* snarfed from linux */
-#define IG4_CTL_MASTER		0x0001	/* snarfed from linux */
 
 /*
  * TAR_ADD - Target Address Register	22.2.2
@@ -413,7 +416,7 @@
  *
  *	Set the SDA hold time length register in I2C clocks.
  */
-#define IG4_SDA_HOLD_MASK	0x00FF
+#define IG4_SDA_TX_HOLD_MASK	0x0000FFFF
 
 /*
  * TX_ABRT_SOURCE- (RO) Transmit Abort Source Register		22.2.27

Modified: head/sys/dev/ichiic/ig4_var.h
==============================================================================
--- head/sys/dev/ichiic/ig4_var.h	Sun Nov  3 20:56:53 2019	(r354305)
+++ head/sys/dev/ichiic/ig4_var.h	Sun Nov  3 20:57:59 2019	(r354306)
@@ -46,6 +46,24 @@
 enum ig4_op { IG4_IDLE, IG4_READ, IG4_WRITE };
 enum ig4_vers { IG4_HASWELL, IG4_ATOM, IG4_SKYLAKE, IG4_APL };
 
+struct ig4_hw {
+	uint32_t	ic_clock_rate;	/* MHz */
+	uint32_t	sda_fall_time;	/* nsec */
+	uint32_t	scl_fall_time;	/* nsec */
+	uint32_t	sda_hold_time;	/* nsec */
+};
+
+struct ig4_cfg {
+	uint32_t	version;
+	uint32_t	bus_speed;
+	uint16_t	ss_scl_hcnt;
+	uint16_t	ss_scl_lcnt;
+	uint16_t	ss_sda_hold;
+	uint16_t	fs_scl_hcnt;
+	uint16_t	fs_scl_lcnt;
+	uint16_t	fs_sda_hold;
+};
+
 struct ig4iic_softc {
 	device_t	dev;
 	device_t	iicbus;
@@ -57,6 +75,7 @@ struct ig4iic_softc {
 	int		intr_type;
 	enum ig4_vers	version;
 	enum ig4_op	op;
+	struct ig4_cfg	cfg;
 	int		cmd;
 	uint32_t	intr_mask;
 	int		error;


More information about the svn-src-all mailing list