summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/soc/intel/apollolake/i2c.c2
-rw-r--r--src/soc/intel/common/lpss_i2c.c209
-rw-r--r--src/soc/intel/common/lpss_i2c.h9
-rw-r--r--src/soc/intel/skylake/i2c.c2
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(&regs->hs_spklen);
+ else
+ spk_cnt = read32(&regs->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();
}