diff options
author | Aaron Durbin <adurbin@chromium.org> | 2016-11-09 23:20:30 -0600 |
---|---|---|
committer | Aaron Durbin <adurbin@chromium.org> | 2016-11-12 00:19:22 +0100 |
commit | 2b3e0cdfc4ddefb85e779fa789ba21406a5f76a3 (patch) | |
tree | f28ff6229c6b6378747fc11b122d0f4c48b89880 | |
parent | 8b5d04e1abd1d7c3b9447385b043b0d902d22a54 (diff) |
soc/intel/common/lpss_i2c: configure buses by rise/fall times
The default register count calculations are leading to higher
frequencies than expected. Provide an alternative method for
calculating the register counts by utilizing the rise and
fall times of the bus. If the rise time is supplied the
rise/fall time values are used, but the register overrides
take precedence over the rise/fall time calculation. This
allows platforms to choose whichever method works the best.
BUG=chrome-os-partner:58889
Change-Id: I7747613ce51d8151848acd916c09ae97bfc4b86a
Signed-off-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: https://review.coreboot.org/17350
Reviewed-by: Furquan Shaikh <furquan@google.com>
Tested-by: build bot (Jenkins)
-rw-r--r-- | src/soc/intel/apollolake/i2c.c | 2 | ||||
-rw-r--r-- | src/soc/intel/common/lpss_i2c.c | 209 | ||||
-rw-r--r-- | src/soc/intel/common/lpss_i2c.h | 9 | ||||
-rw-r--r-- | src/soc/intel/skylake/i2c.c | 2 |
4 files changed, 215 insertions, 7 deletions
diff --git a/src/soc/intel/apollolake/i2c.c b/src/soc/intel/apollolake/i2c.c index 69cb4b8d5f..bb30c51c26 100644 --- a/src/soc/intel/apollolake/i2c.c +++ b/src/soc/intel/apollolake/i2c.c @@ -77,7 +77,7 @@ static void i2c_fill_ssdt(struct device *dev) return; acpigen_write_scope(acpi_device_path(dev)); - lpss_i2c_acpi_fill_ssdt(&config->i2c[bus]); + lpss_i2c_acpi_fill_ssdt(bus, &config->i2c[bus]); acpigen_pop_len(); } diff --git a/src/soc/intel/common/lpss_i2c.c b/src/soc/intel/common/lpss_i2c.c index 51c59154ec..49fb85d6a6 100644 --- a/src/soc/intel/common/lpss_i2c.c +++ b/src/soc/intel/common/lpss_i2c.c @@ -24,6 +24,8 @@ #include <timer.h> #include "lpss_i2c.h" +#define LPSS_DEBUG BIOS_NEVER + struct lpss_i2c_regs { uint32_t control; uint32_t target_addr; @@ -94,6 +96,78 @@ enum { MIN_HS_SCL_LOWTIME = 160, }; +/* Frequency represented as ticks per ns. Can also be used to calculate + * the number of ticks to meet a time target or the period. */ +struct freq { + uint32_t ticks; + uint32_t ns; +}; + +static const struct i2c_descriptor { + enum i2c_speed speed; + struct freq freq; + int min_thigh_ns; + int min_tlow_ns; +} speed_descriptors[] = { + { + .speed = I2C_SPEED_STANDARD, + .freq = { + .ticks = 100, + .ns = 1000*1000, + }, + .min_thigh_ns = MIN_SS_SCL_HIGHTIME, + .min_tlow_ns = MIN_SS_SCL_LOWTIME, + }, + { + .speed = I2C_SPEED_FAST, + .freq = { + .ticks = 400, + .ns = 1000*1000, + }, + .min_thigh_ns = MIN_FS_SCL_HIGHTIME, + .min_tlow_ns = MIN_FS_SCL_LOWTIME, + }, + { + .speed = I2C_SPEED_FAST_PLUS, + .freq = { + .ticks = 1, + .ns = 1000, + }, + .min_thigh_ns = MIN_FP_SCL_HIGHTIME, + .min_tlow_ns = MIN_FP_SCL_LOWTIME, + }, + { + /* 100pF max capacitance */ + .speed = I2C_SPEED_HIGH, + .freq = { + .ticks = 3400, + .ns = 1000*1000, + }, + .min_thigh_ns = MIN_HS_SCL_HIGHTIME, + .min_tlow_ns = MIN_HS_SCL_LOWTIME, + }, +}; + +static const struct soc_clock { + int clk_speed_mhz; + struct freq freq; +} soc_clocks[] = { + { + .clk_speed_mhz = 120, + .freq = { + .ticks = 120, + .ns = 1000, + }, + }, + { + .clk_speed_mhz = 133, + .freq = { + .ticks = 400, + .ns = 3000, + }, + }, +}; + /* Control register definitions */ enum { CONTROL_MASTER_MODE = (1 << 0), @@ -145,6 +219,38 @@ enum { INTR_STAT_GEN_CALL = (1 << 11), }; +static const struct i2c_descriptor *get_bus_descriptor(enum i2c_speed speed) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(speed_descriptors); i++) + if (speed == speed_descriptors[i].speed) + return &speed_descriptors[i]; + + return NULL; +} + +static const struct soc_clock *get_soc_descriptor(int ic_clk) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(soc_clocks); i++) + if (ic_clk == soc_clocks[i].clk_speed_mhz) + return &soc_clocks[i]; + + return NULL; +} + +static int counts_from_time(const struct freq *f, int ns) +{ + return DIV_ROUND_UP(f->ticks * ns, f->ns); +} + +static int counts_from_freq(const struct freq *fast, const struct freq *slow) +{ + return DIV_ROUND_UP(fast->ticks * slow->ns, fast->ns * slow->ticks); +} + /* Enable this I2C controller */ static void lpss_i2c_enable(struct lpss_i2c_regs *regs) { @@ -411,7 +517,91 @@ static int lpss_i2c_set_speed_config(unsigned bus, return 0; } -static int lpss_i2c_gen_speed_config(enum i2c_speed speed, +static int lpss_i2c_gen_config_rise_fall_time(struct lpss_i2c_regs *regs, + enum i2c_speed speed, + const struct lpss_i2c_bus_config *bcfg, + int ic_clk, + struct lpss_i2c_speed_config *config) +{ + const struct i2c_descriptor *bus; + const struct soc_clock *soc; + int fall_cnt, rise_cnt, min_tlow_cnt, min_thigh_cnt, spk_cnt; + int hcnt, lcnt, period_cnt, diff, tot; + + bus = get_bus_descriptor(speed); + soc = get_soc_descriptor(ic_clk); + + if (bus == NULL) { + printk(BIOS_ERR, "lpss_i2c: invalid bus speed %d\n", + config->speed); + return -1; + } + + if (soc == NULL) { + printk(BIOS_ERR, "lpss_i2c: invalid SoC clock speed %d MHz\n", + soc->clk_speed_mhz); + return -1; + } + + /* Get the proper spike suppression count based on target speed. */ + if (speed >= I2C_SPEED_HIGH) + spk_cnt = read32(®s->hs_spklen); + else + spk_cnt = read32(®s->fs_spklen); + + /* Find the period, rise, fall, min tlow, and min thigh in terms of + * counts of SoC clock. */ + period_cnt = counts_from_freq(&soc->freq, &bus->freq); + rise_cnt = counts_from_time(&soc->freq, bcfg->rise_time_ns); + fall_cnt = counts_from_time(&soc->freq, bcfg->fall_time_ns); + min_tlow_cnt = counts_from_time(&soc->freq, bus->min_tlow_ns); + min_thigh_cnt = counts_from_time(&soc->freq, bus->min_thigh_ns); + + printk(LPSS_DEBUG, "lpss_i2c: SoC %d/%d ns Bus: %d/%d ns\n", + soc->freq.ticks, soc->freq.ns, bus->freq.ticks, bus->freq.ns); + printk(LPSS_DEBUG, "lpss_i2c: period %d rise %d fall %d tlow %d thigh %d spk %d\n", + period_cnt, rise_cnt, fall_cnt, min_tlow_cnt, min_thigh_cnt, + spk_cnt); + + /* + * Back solve for hcnt and lcnt according to the following equations. + * SCL_High_time = [(HCNT + IC_*_SPKLEN + 7) * ic_clk] + SCL_Fall_time + * SCL_Low_time = [(LCNT + 1) * ic_clk] - SCL_Fall_time + SCL_Rise_time + */ + hcnt = min_thigh_cnt - fall_cnt - 7 - spk_cnt; + lcnt = min_tlow_cnt - rise_cnt + fall_cnt - 1; + + if (hcnt < 0 || lcnt < 0) { + printk(BIOS_ERR, "lpss_i2c: bad counts. hcnt = %d lcnt = %d\n", + hcnt, lcnt); + return -1; + } + + /* Now add things back up to ensure the period is hit. If off, + * split the difference and bias to lcnt for remainder. */ + tot = hcnt + lcnt + 7 + spk_cnt + rise_cnt + 1; + + if (tot < period_cnt) { + diff = (period_cnt - tot) / 2; + hcnt += diff; + lcnt += diff; + tot = hcnt + lcnt + 7 + spk_cnt + rise_cnt + 1; + lcnt += period_cnt - tot; + } + + config->speed = speed; + config->scl_lcnt = lcnt; + config->scl_hcnt = hcnt; + config->sda_hold = counts_from_time(&soc->freq, DEFAULT_SDA_HOLD_TIME); + + printk(LPSS_DEBUG, "lpss_i2c: hcnt = %d lcnt = %d sda hold = %d\n", + hcnt, lcnt, config->sda_hold); + + return 0; +} + +static int lpss_i2c_gen_speed_config(struct lpss_i2c_regs *regs, + enum i2c_speed speed, const struct lpss_i2c_bus_config *bcfg, struct lpss_i2c_speed_config *config) { @@ -431,6 +621,11 @@ static int lpss_i2c_gen_speed_config(enum i2c_speed speed, return 0; } + /* If rise time is set use the time calculation. */ + if (bcfg->rise_time_ns) + return lpss_i2c_gen_config_rise_fall_time(regs, speed, bcfg, + ic_clk, config); + if (speed >= I2C_SPEED_HIGH) { /* High speed */ hcnt_min = MIN_HS_SCL_HIGHTIME; @@ -484,7 +679,7 @@ static int lpss_i2c_set_speed(unsigned bus, enum i2c_speed speed, } /* Generate speed config based on clock */ - if (lpss_i2c_gen_speed_config(speed, bcfg, &config) < 0) + if (lpss_i2c_gen_speed_config(regs, speed, bcfg, &config) < 0) return -1; /* Select this speed in the control register */ @@ -496,8 +691,10 @@ static int lpss_i2c_set_speed(unsigned bus, enum i2c_speed speed, return 0; } -void lpss_i2c_acpi_fill_ssdt(const struct lpss_i2c_bus_config *bcfg) +void lpss_i2c_acpi_fill_ssdt(unsigned bus, + const struct lpss_i2c_bus_config *bcfg) { + struct lpss_i2c_regs *regs; struct lpss_i2c_speed_config sgen; enum i2c_speed speeds[LPSS_I2C_SPEED_CONFIG_COUNT] = { I2C_SPEED_STANDARD, @@ -510,10 +707,14 @@ void lpss_i2c_acpi_fill_ssdt(const struct lpss_i2c_bus_config *bcfg) if (!bcfg) return; + regs = (struct lpss_i2c_regs *)lpss_i2c_base_address(bus); + if (!regs) + return; + /* Report timing values for the OS driver */ for (i = 0; i < LPSS_I2C_SPEED_CONFIG_COUNT; i++) { /* Generate speed config. */ - if (lpss_i2c_gen_speed_config(speeds[i], bcfg, &sgen) < 0) + if (lpss_i2c_gen_speed_config(regs, speeds[i], bcfg, &sgen) < 0) continue; /* Generate ACPI based on selected speed config */ diff --git a/src/soc/intel/common/lpss_i2c.h b/src/soc/intel/common/lpss_i2c.h index f4a48bddef..956697356d 100644 --- a/src/soc/intel/common/lpss_i2c.h +++ b/src/soc/intel/common/lpss_i2c.h @@ -55,6 +55,12 @@ struct lpss_i2c_bus_config { int early_init; /* Bus speed in Hz, default is I2C_SPEED_FAST (400 KHz) */ enum i2c_speed speed; + /* If rise_time_ns is non-zero the calculations for lcnt and hcnt + * registers take into account the times of the bus. However, if + * there is a match in speed_config those register values take + * precedence. */ + int rise_time_ns; + int fall_time_ns; /* Specific bus speed configuration */ struct lpss_i2c_speed_config speed_config[LPSS_I2C_SPEED_CONFIG_COUNT]; }; @@ -79,7 +85,8 @@ uintptr_t lpss_i2c_base_address(unsigned bus); * Generate I2C timing information into the SSDT for the OS driver to consume, * optionally applying override values provided by the caller. */ -void lpss_i2c_acpi_fill_ssdt(const struct lpss_i2c_bus_config *bcfg); +void lpss_i2c_acpi_fill_ssdt(unsigned bus, + const struct lpss_i2c_bus_config *bcfg); /* * Initialize this bus controller and set the speed. diff --git a/src/soc/intel/skylake/i2c.c b/src/soc/intel/skylake/i2c.c index c8f02943d1..d12323f94f 100644 --- a/src/soc/intel/skylake/i2c.c +++ b/src/soc/intel/skylake/i2c.c @@ -74,7 +74,7 @@ static void i2c_fill_ssdt(struct device *dev) return; acpigen_write_scope(acpi_device_path(dev)); - lpss_i2c_acpi_fill_ssdt(&config->i2c[bus]); + lpss_i2c_acpi_fill_ssdt(bus, &config->i2c[bus]); acpigen_pop_len(); } |