[coreboot-gerrit] New patch to review for coreboot: lpss_i2c: Set SDA hold and support custom speed config

Duncan Laurie (dlaurie@chromium.org) gerrit at coreboot.org
Mon Jun 13 20:34:57 CEST 2016


Duncan Laurie (dlaurie at chromium.org) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/15163

-gerrit

commit 4a9f6f1b40f7c10a480c29e48155013472226b85
Author: Duncan Laurie <dlaurie at chromium.org>
Date:   Mon Jun 13 10:28:36 2016 -0700

    lpss_i2c: Set SDA hold and support custom speed config
    
    This I2C controller has separate registers for different speeds to set
    specific timing for SCL high and low times, and then a single register
    to configure the SDA hold time.
    
    For the most part these values can be generated based on the freq of
    the controller clock, which is SOC-specific.  The existing driver was
    generating SCL HCNT/LCNT values, but not the SDA hold time so that is
    added.
    
    Additionally a board may need custom values as the exact timing can
    depend on trace lengths and the number of devices on the I2C bus.
    This is a two-part customizaton, the first is to set the values for
    desired speed for use within firmware, and the second is to provide
    those values in ACPI for the OS driver to consume.
    
    Since these custom speed configs will come from devicetree a macro is
    added to simplify the description:
    
    register "i2c[4].speed_config" = "{
    	 LPSS_I2C_SPEED_CONFIG(STANDARD, 432, 507, 30),
    	 LPSS_I2C_SPEED_CONFIG(FAST, 72, 160, 30),
    }"
    
    Which will result in the following speed config in \_SB.PCI0.I2C4:
    
    Name (SSCN, Package () { 432, 507, 30 })
    Name (FMCN, Package () { 72, 160, 30 })
    
    Change-Id: I18964426bb83fad0c956ad43a36ed9e04f3a66b5
    Signed-off-by: Duncan Laurie <dlaurie at chromium.org>
---
 src/soc/intel/common/lpss_i2c.c | 98 ++++++++++++++++++++++++++++++++++-------
 src/soc/intel/common/lpss_i2c.h | 69 +++++++++++++++++++++++++++++
 2 files changed, 151 insertions(+), 16 deletions(-)

diff --git a/src/soc/intel/common/lpss_i2c.c b/src/soc/intel/common/lpss_i2c.c
index 2ac8e66..58f1abc 100644
--- a/src/soc/intel/common/lpss_i2c.c
+++ b/src/soc/intel/common/lpss_i2c.c
@@ -14,6 +14,7 @@
  * GNU General Public License for more details.
  */
 
+#include <arch/acpigen.h>
 #include <arch/io.h>
 #include <commonlib/helpers.h>
 #include <console/console.h>
@@ -63,6 +64,8 @@ struct lpss_i2c_regs {
 
 /* High and low times in different speed modes (in ns) */
 enum {
+	/* SDA Hold Time */
+	DEFAULT_SDA_HOLD_TIME		= 300,
 	/* Standard Speed */
 	MIN_SS_SCL_HIGHTIME		= 4000,
 	MIN_SS_SCL_LOWTIME		= 4700,
@@ -299,48 +302,111 @@ int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int count)
 	return 0;
 }
 
-static void lpss_i2c_set_speed(struct lpss_i2c_regs *regs, enum i2c_speed speed)
+void lpss_i2c_acpi_write_speed_config(
+	const struct lpss_i2c_speed_config *config)
 {
-	const int ic_clk = CONFIG_SOC_INTEL_COMMON_LPSS_I2C_CLOCK_MHZ;
-	uint32_t control, hcnt_min, lcnt_min;
+	if (!config)
+		return;
+	if (!config->scl_lcnt && !config->scl_hcnt && !config->sda_hold)
+		return;
+
+	switch (config->speed) {
+	case I2C_SPEED_STANDARD:
+		acpigen_write_name("SSCN");
+		break;
+	case I2C_SPEED_FAST:
+		acpigen_write_name("FMCN");
+		break;
+	default:
+		return;
+	}
+
+	/* Package () { scl_lcnt, scl_hcnt, sda_hold } */
+	acpigen_write_package(3);
+	acpigen_write_word(config->scl_hcnt);
+	acpigen_write_word(config->scl_lcnt);
+	acpigen_write_dword(config->sda_hold);
+	acpigen_pop_len();
+}
+
+void lpss_i2c_set_speed_config(unsigned bus,
+			       const struct lpss_i2c_speed_config *config)
+{
+	struct lpss_i2c_regs *regs;
 	void *hcnt_reg, *lcnt_reg;
 
+	regs = (struct lpss_i2c_regs *)lpss_i2c_base_address(bus);
+	if (!regs || !config)
+		return;
+	if (!config->scl_lcnt && !config->scl_hcnt && !config->sda_hold)
+		return;
+
+	if (config->speed >= I2C_SPEED_FAST_PLUS) {
+		/* Fast-Plus and High Speed */
+		hcnt_reg = &regs->hs_scl_hcnt;
+		lcnt_reg = &regs->hs_scl_lcnt;
+	} else if (config->speed >= I2C_SPEED_FAST) {
+		/* Fast Speed */
+		hcnt_reg = &regs->fs_scl_hcnt;
+		lcnt_reg = &regs->fs_scl_lcnt;
+	} else {
+		/* Standard Speed */
+		hcnt_reg = &regs->ss_scl_hcnt;
+		lcnt_reg = &regs->ss_scl_lcnt;
+	}
+
+	/* SCL count must be set after the speed is selected */
+	if (config->scl_hcnt)
+		write32(hcnt_reg, config->scl_hcnt);
+	if (config->scl_lcnt)
+		write32(lcnt_reg, config->scl_lcnt);
+
+	/* Set SDA Hold Time register */
+	if (config->sda_hold)
+		write32(&regs->sda_hold, config->sda_hold);
+}
+
+void lpss_i2c_set_speed(unsigned bus, enum i2c_speed speed)
+{
+	const int ic_clk = CONFIG_SOC_INTEL_COMMON_LPSS_I2C_CLOCK_MHZ;
+	struct lpss_i2c_regs *regs;
+	struct lpss_i2c_speed_config config;
+	uint16_t hcnt_min, lcnt_min;
+	uint32_t control;
+
 	/* Clock must be provided by Kconfig */
-	if (!ic_clk || !speed)
+	regs = (struct lpss_i2c_regs *)lpss_i2c_base_address(bus);
+	if (!regs || !ic_clk || !speed)
 		return;
 
 	control = read32(&regs->control);
 	control &= ~CONTROL_SPEED_MASK;
 
-	if (speed >= I2C_SPEED_HIGH) {
-		/* High Speed */
+	if (speed >= I2C_SPEED_FAST_PLUS) {
+		/* Fast-Plus and High Speed */
 		control |= CONTROL_SPEED_HS;
-		hcnt_reg = &regs->hs_scl_hcnt;
-		lcnt_reg = &regs->hs_scl_lcnt;
 		hcnt_min = MIN_HS_SCL_HIGHTIME;
 		lcnt_min = MIN_HS_SCL_LOWTIME;
 	} else if (speed >= I2C_SPEED_FAST) {
 		/* Fast Speed */
 		control |= CONTROL_SPEED_FS;
-		hcnt_reg = &regs->fs_scl_hcnt;
-		lcnt_reg = &regs->fs_scl_lcnt;
 		hcnt_min = MIN_FS_SCL_HIGHTIME;
 		lcnt_min = MIN_FS_SCL_LOWTIME;
 	} else {
 		/* Standard Speed */
 		control |= CONTROL_SPEED_SS;
-		hcnt_reg = &regs->ss_scl_hcnt;
-		lcnt_reg = &regs->ss_scl_lcnt;
 		hcnt_min = MIN_SS_SCL_HIGHTIME;
 		lcnt_min = MIN_SS_SCL_LOWTIME;
 	}
 
+	config.scl_hcnt = ic_clk * hcnt_min / KHz;
+	config.scl_lcnt = ic_clk * lcnt_min / KHz;
+	config.sda_hold = ic_clk * DEFAULT_SDA_HOLD_TIME / KHz;
+
 	/* Select this speed in the control register */
 	write32(&regs->control, control);
 
-	/* SCL count must be set after the speed is selected */
-	write32(hcnt_reg, ic_clk * hcnt_min / KHz);
-	write32(lcnt_reg, ic_clk * lcnt_min / KHz);
+	lpss_i2c_set_speed_config(bus, &config);
 }
 
 void lpss_i2c_init(unsigned bus, enum i2c_speed speed)
@@ -363,7 +429,7 @@ void lpss_i2c_init(unsigned bus, enum i2c_speed speed)
 		CONTROL_RESTART_ENABLE);
 
 	/* Set bus speed to FAST by default */
-	lpss_i2c_set_speed(regs, speed ? : I2C_SPEED_FAST);
+	lpss_i2c_set_speed(bus, speed ? : I2C_SPEED_FAST);
 
 	/* Set RX/TX thresholds to smallest values */
 	write32(&regs->rx_thresh, 0);
diff --git a/src/soc/intel/common/lpss_i2c.h b/src/soc/intel/common/lpss_i2c.h
index 8259414..5d1272b 100644
--- a/src/soc/intel/common/lpss_i2c.h
+++ b/src/soc/intel/common/lpss_i2c.h
@@ -20,6 +20,45 @@
 #include <stdint.h>
 
 /*
+ * Timing values are in units of clock period, with the clock speed
+ * provided by the SOC in CONFIG_SOC_INTEL_COMMON_LPSS_I2C_CLOCK_MHZ.
+ * Automatic configuration is done based on requested speed, but the
+ * values may need tuned depending on the board and the number of
+ * devices present on the bus.
+ */
+struct lpss_i2c_speed_config {
+	enum i2c_speed speed;
+	/* SCL high and low period count */
+	uint16_t scl_lcnt;
+	uint16_t scl_hcnt;
+	/*
+	 * SDA hold time should be 300ns in standard and fast modes
+	 * and long enough for deterministic logic level change in
+	 * fast-plus and high speed modes.
+	 *
+	 *  [15:0] SDA TX Hold Time
+	 * [23:16] SDA RX Hold Time
+	 */
+	uint32_t sda_hold;
+};
+
+#define LPSS_I2C_SPEED_CONFIG(speedval,lcnt,hcnt,hold)	\
+	{						\
+		.speed = I2C_SPEED_ ## speedval,	\
+		.scl_lcnt = (lcnt),			\
+		.scl_hcnt = (hcnt),			\
+		.sda_hold = (hold),			\
+	}
+
+/*
+ * I2C controller has support for 3 independent speed configs:
+ * 1) STANDARD: <= 100 KHz
+ * 2) FAST: <= 400 KHz
+ * 3) FAST_PLUS / HIGH: > 400 KHz
+ */
+#define LPSS_I2C_SPEED_CONFIG_COUNT	3
+
+/*
  * Return the base address for this bus controller.
  *
  * This function *must* be implemented by the SOC and return the appropriate
@@ -28,6 +67,36 @@
 uintptr_t lpss_i2c_base_address(unsigned bus);
 
 /*
+ * Set raw speed configuration for given speed type.
+ *
+ * This allows a SOC or board to override the automatic bus speed calculation
+ * and provided specific values for the driver to use.
+ */
+void lpss_i2c_set_speed_config(unsigned bus,
+       const struct lpss_i2c_speed_config *config);
+
+/*
+ * Write ACPI object to describe speed configuration.
+ *
+ * LPSS_I2C_SPEED_STANDARD:
+ *   Name ("SSCN", Package () { scl_lcnt, scl_hcnt, sda_hold }
+ *
+ * LPSS_I2C_SPEED_FAST:
+ *   Name ("FMCN", Package () { scl_lcnt, scl_hcnt, sda_hold }
+ */
+void lpss_i2c_acpi_write_speed_config(
+	const struct lpss_i2c_speed_config *config);
+
+/*
+ * Set I2C bus speed for this controller.
+ *
+ * This allows an SOC or board to set the basic I2C bus speed.  Values for the
+ * controller configuration registers will be calculated, for more specific
+ * control the raw configuration can be provided to lpss_i2c_set_speed_config().
+ */
+void lpss_i2c_set_speed(unsigned bus, enum i2c_speed speed);
+
+/*
  * Initialize this bus controller and set the speed.
  *
  * The bus speed can be passed in Hz or using values from device/i2c.h and



More information about the coreboot-gerrit mailing list