From 88a1f14cad126103a83a045e3eded595d58515ca Mon Sep 17 00:00:00 2001 From: Duncan Laurie Date: Mon, 13 Jun 2016 10:28:36 -0700 Subject: 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. And finally, recent upstream changes to the designware i2c driver in the Linux kernel now support passing custom timing values for high speed and fast-plus speed, so these are now supported as well. 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), LPSS_I2C_SPEED_CONFIG(FAST_PLUS, 52, 120, 30), LPSS_I2C_SPEED_CONFIG(HIGH, 38, 90, 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 }) Name (FPCN, Package () { 52, 120, 30 }) Name (HSCN, Package () { 38, 90, 30 }) Change-Id: I18964426bb83fad0c956ad43a36ed9e04f3a66b5 Signed-off-by: Duncan Laurie Reviewed-on: https://review.coreboot.org/15163 Tested-by: build bot (Jenkins) Reviewed-by: Aaron Durbin --- src/soc/intel/common/lpss_i2c.c | 161 +++++++++++++++++++++++++++++++++------- src/soc/intel/common/lpss_i2c.h | 81 +++++++++++++++++++- 2 files changed, 214 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/soc/intel/common/lpss_i2c.c b/src/soc/intel/common/lpss_i2c.c index 2ac8e667c7..bb684e2f35 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 #include #include #include @@ -63,12 +64,17 @@ 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, - /* Fast/Fast+ Speed */ + /* Fast Speed */ MIN_FS_SCL_HIGHTIME = 600, MIN_FS_SCL_LOWTIME = 1300, + /* Fast Plus Speed */ + MIN_FP_SCL_HIGHTIME = 260, + MIN_FP_SCL_LOWTIME = 500, /* High Speed */ MIN_HS_SCL_HIGHTIME = 60, MIN_HS_SCL_LOWTIME = 160, @@ -299,63 +305,159 @@ 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; + + if (config->speed >= I2C_SPEED_HIGH) + acpigen_write_name("HSCN"); + else if (config->speed >= I2C_SPEED_FAST_PLUS) + acpigen_write_name("FPCN"); + else if (config->speed >= I2C_SPEED_FAST) + acpigen_write_name("FMCN"); + else + acpigen_write_name("SSCN"); + + /* 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(); +} + +int lpss_i2c_set_speed_config(unsigned bus, + const struct lpss_i2c_speed_config *config) +{ + struct lpss_i2c_regs *regs; void *hcnt_reg, *lcnt_reg; - /* Clock must be provided by Kconfig */ - if (!ic_clk || !speed) - return; + regs = (struct lpss_i2c_regs *)lpss_i2c_base_address(bus); + if (!regs || !config) + return -1; - control = read32(®s->control); - control &= ~CONTROL_SPEED_MASK; + /* Nothing to do if no values are set */ + if (!config->scl_lcnt && !config->scl_hcnt && !config->sda_hold) + return 0; - if (speed >= I2C_SPEED_HIGH) { - /* High Speed */ - control |= CONTROL_SPEED_HS; + if (config->speed >= I2C_SPEED_FAST_PLUS) { + /* Fast-Plus and High speed */ hcnt_reg = ®s->hs_scl_hcnt; lcnt_reg = ®s->hs_scl_lcnt; + } else if (config->speed >= I2C_SPEED_FAST) { + /* Fast speed */ + hcnt_reg = ®s->fs_scl_hcnt; + lcnt_reg = ®s->fs_scl_lcnt; + } else { + /* Standard speed */ + hcnt_reg = ®s->ss_scl_hcnt; + lcnt_reg = ®s->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(®s->sda_hold, config->sda_hold); + + return 0; +} + +int lpss_i2c_gen_speed_config(enum i2c_speed speed, + struct lpss_i2c_speed_config *config) +{ + const int ic_clk = CONFIG_SOC_INTEL_COMMON_LPSS_I2C_CLOCK_MHZ; + uint16_t hcnt_min, lcnt_min; + + /* Clock must be provided by Kconfig */ + if (!ic_clk || !config) + return -1; + + if (speed >= I2C_SPEED_HIGH) { + /* High speed */ hcnt_min = MIN_HS_SCL_HIGHTIME; lcnt_min = MIN_HS_SCL_LOWTIME; + } else if (speed >= I2C_SPEED_FAST_PLUS) { + /* Fast-Plus speed */ + hcnt_min = MIN_FP_SCL_HIGHTIME; + lcnt_min = MIN_FP_SCL_LOWTIME; } else if (speed >= I2C_SPEED_FAST) { - /* Fast Speed */ - control |= CONTROL_SPEED_FS; - hcnt_reg = ®s->fs_scl_hcnt; - lcnt_reg = ®s->fs_scl_lcnt; + /* Fast speed */ hcnt_min = MIN_FS_SCL_HIGHTIME; lcnt_min = MIN_FS_SCL_LOWTIME; } else { - /* Standard Speed */ - control |= CONTROL_SPEED_SS; - hcnt_reg = ®s->ss_scl_hcnt; - lcnt_reg = ®s->ss_scl_lcnt; + /* Standard speed */ hcnt_min = MIN_SS_SCL_HIGHTIME; lcnt_min = MIN_SS_SCL_LOWTIME; } + config->speed = speed; + 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; + + return 0; +} + +int lpss_i2c_set_speed(unsigned bus, enum i2c_speed speed) +{ + struct lpss_i2c_regs *regs; + struct lpss_i2c_speed_config config; + uint32_t control; + + /* Clock must be provided by Kconfig */ + regs = (struct lpss_i2c_regs *)lpss_i2c_base_address(bus); + if (!regs || !speed) + return -1; + + control = read32(®s->control); + control &= ~CONTROL_SPEED_MASK; + + if (speed >= I2C_SPEED_FAST_PLUS) { + /* High and Fast-Plus speed share config registers */ + control |= CONTROL_SPEED_HS; + } else if (speed >= I2C_SPEED_FAST) { + /* Fast speed */ + control |= CONTROL_SPEED_FS; + } else { + /* Standard speed */ + control |= CONTROL_SPEED_SS; + } + + /* Generate speed config based on clock */ + if (lpss_i2c_gen_speed_config(speed, &config) < 0) + return -1; + /* Select this speed in the control register */ write32(®s->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); + /* Write the speed config that was generated earlier */ + lpss_i2c_set_speed_config(bus, &config); + + return 0; } -void lpss_i2c_init(unsigned bus, enum i2c_speed speed) +int lpss_i2c_init(unsigned bus, enum i2c_speed speed) { struct lpss_i2c_regs *regs; regs = (struct lpss_i2c_regs *)lpss_i2c_base_address(bus); if (!regs) { printk(BIOS_ERR, "I2C bus %u base address not found\n", bus); - return; + return -1; } if (lpss_i2c_disable(regs) < 0) { printk(BIOS_ERR, "I2C timeout disabling bus %u\n", bus); - return; + return -1; } /* Put controller in master mode with restart enabled */ @@ -363,7 +465,10 @@ 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); + if (lpss_i2c_set_speed(bus, speed ? : I2C_SPEED_FAST) < 0) { + printk(BIOS_ERR, "I2C failed to set speed for bus %u\n", bus); + return -1; + } /* Set RX/TX thresholds to smallest values */ write32(®s->rx_thresh, 0); @@ -376,4 +481,6 @@ void lpss_i2c_init(unsigned bus, enum i2c_speed speed) printk(BIOS_INFO, "LPSS I2C bus %u at 0x%p (%u KHz)\n", bus, regs, (speed ? : I2C_SPEED_FAST) / KHz); + + return 0; } diff --git a/src/soc/intel/common/lpss_i2c.h b/src/soc/intel/common/lpss_i2c.h index 82594147fd..1ca23b59e8 100644 --- a/src/soc/intel/common/lpss_i2c.h +++ b/src/soc/intel/common/lpss_i2c.h @@ -19,6 +19,45 @@ #include #include +/* + * 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; +}; + +/* + * This I2C controller has support for 3 independent speed configs but can + * support both FAST_PLUS and HIGH speeds through the same set of speed + * config registers. These are treated separately so the speed config values + * can be provided via ACPI to the OS. + */ +#define LPSS_I2C_SPEED_CONFIG_COUNT 4 + +#define LPSS_I2C_SPEED_CONFIG(speedval,lcnt,hcnt,hold) \ + { \ + .speed = I2C_SPEED_ ## speedval, \ + .scl_lcnt = (lcnt), \ + .scl_hcnt = (hcnt), \ + .sda_hold = (hold), \ + } + /* * Return the base address for this bus controller. * @@ -27,6 +66,46 @@ */ uintptr_t lpss_i2c_base_address(unsigned bus); +/* + * Generate speed configuration for requested controller and bus speed. + * + * This allows a SOC or board to automatically generate speed config to use + * in firmware and provide to the OS. + */ +int lpss_i2c_gen_speed_config(enum i2c_speed speed, + struct lpss_i2c_speed_config *config); + +/* + * 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. + */ +int lpss_i2c_set_speed_config(unsigned bus, + const struct lpss_i2c_speed_config *config); + +/* + * Write ACPI object to describe speed configuration. + * + * ACPI Object: Name ("xxxx", Package () { scl_lcnt, scl_hcnt, sda_hold } + * + * SSCN: I2C_SPEED_STANDARD + * FMCN: I2C_SPEED_FAST + * FPCN: I2C_SPEED_FAST_PLUS + * HSCN: I2C_SPEED_HIGH + */ +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(). + */ +int lpss_i2c_set_speed(unsigned bus, enum i2c_speed speed); + /* * Initialize this bus controller and set the speed. * @@ -36,6 +115,6 @@ uintptr_t lpss_i2c_base_address(unsigned bus); * The SOC *must* define CONFIG_SOC_INTEL_COMMON_LPSS_I2C_CLOCK for the * bus speed calculation to be correct. */ -void lpss_i2c_init(unsigned bus, enum i2c_speed speed); +int lpss_i2c_init(unsigned bus, enum i2c_speed speed); #endif -- cgit v1.2.3