diff --git a/src/include/cpu/power/mvpd.h b/src/include/cpu/power/mvpd.h index 39dfe9ab6b6..cda62ff4a0a 100644 --- a/src/include/cpu/power/mvpd.h +++ b/src/include/cpu/power/mvpd.h @@ -6,6 +6,44 @@ #include #include +/* Single bucket within #V keyword of version 3 */ +struct voltage_data +{ + uint16_t freq; + uint16_t vdd_voltage; + uint16_t idd_current; + uint16_t vcs_voltage; + uint16_t ics_current; +} __attribute__((__packed__)); + +/* Single bucket within #V keyword of version 3 */ +struct voltage_bucket_data +{ + uint8_t id; + + struct voltage_data nominal; + struct voltage_data powersave; + struct voltage_data turbo; + struct voltage_data ultra_turbo; + struct voltage_data powerbus; + + uint16_t sort_power_normal; + uint16_t sort_power_turbo; + + uint8_t reserved[6]; +} __attribute__((__packed__)); + +#define VOLTAGE_DATA_VERSION 3 +#define VOLTAGE_BUCKET_COUNT 6 + +/* #V of LRP[0-5] in MVPD */ +struct voltage_kwd +{ + uint8_t version; + uint8_t pnp[3]; + struct voltage_bucket_data buckets[VOLTAGE_BUCKET_COUNT]; +} __attribute__((__packed__)); + struct region_device; void mvpd_pnor_main(void); @@ -16,6 +54,15 @@ void mvpd_device_unmount(void); const struct region_device *mvpd_device_ro(void); +/* Reads #V of one of LRP records (mind that there is only one buffer) */ +const struct voltage_kwd *mvpd_get_voltage_data(int lrp); + +/* Finds a specific keyword in MVPD partition and extracts it. *size is updated + * to reflect needed or used space in the buffer. */ +bool mvpd_extract_keyword(const char *record_name, const char *kwd_name, + uint8_t *buf, uint32_t *size); + +/* Finds a specific ring in MVPD partition and extracts it */ bool mvpd_extract_ring(const char *record_name, const char *kwd_name, uint8_t chiplet_id, uint16_t ring_id, uint8_t *buf, uint32_t buf_size); diff --git a/src/include/cpu/power/powerbus.h b/src/include/cpu/power/powerbus.h new file mode 100644 index 00000000000..d2598b9648a --- /dev/null +++ b/src/include/cpu/power/powerbus.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef CPU_PPC64_POWERBUS_H +#define CPU_PPC64_POWERBUS_H + +#include + +enum FABRIC_CORE_FLOOR_RATIO +{ + FABRIC_CORE_FLOOR_RATIO_RATIO_8_8 = 0x0, + FABRIC_CORE_FLOOR_RATIO_RATIO_7_8 = 0x1, + FABRIC_CORE_FLOOR_RATIO_RATIO_6_8 = 0x2, + FABRIC_CORE_FLOOR_RATIO_RATIO_5_8 = 0x3, + FABRIC_CORE_FLOOR_RATIO_RATIO_4_8 = 0x4, + FABRIC_CORE_FLOOR_RATIO_RATIO_2_8 = 0x5, +}; + +enum FABRIC_CORE_CEILING_RATIO +{ + FABRIC_CORE_CEILING_RATIO_RATIO_8_8 = 0x0, + FABRIC_CORE_CEILING_RATIO_RATIO_7_8 = 0x1, + FABRIC_CORE_CEILING_RATIO_RATIO_6_8 = 0x2, + FABRIC_CORE_CEILING_RATIO_RATIO_5_8 = 0x3, + FABRIC_CORE_CEILING_RATIO_RATIO_4_8 = 0x4, + FABRIC_CORE_CEILING_RATIO_RATIO_2_8 = 0x5, +}; + +#define NUM_EPSILON_READ_TIERS 3 +#define NUM_EPSILON_WRITE_TIERS 2 + +/* Description of PowerBus configuration */ +struct powerbus_cfg +{ + /* Data computed from #V of LRP0 in MVPD, is MHz */ + uint32_t freq_core_floor; + uint32_t freq_core_ceiling; + uint32_t fabric_freq; + + /* Derived from data above */ + enum FABRIC_CORE_FLOOR_RATIO core_floor_ratio; + enum FABRIC_CORE_CEILING_RATIO core_ceiling_ratio; + + /* Derived from all data above */ + /* ATTR_PROC_EPS_READ_CYCLES_T* */ + uint32_t eps_r[NUM_EPSILON_READ_TIERS]; + /* ATTR_PROC_EPS_WRITE_CYCLES_T* */ + uint32_t eps_w[NUM_EPSILON_WRITE_TIERS]; +}; + +const struct powerbus_cfg *powerbus_cfg(void); + +#endif // CPU_PPC64_POWERBUS_H diff --git a/src/soc/ibm/power9/Makefile.inc b/src/soc/ibm/power9/Makefile.inc index 1947126a1f9..eb76fe4625e 100644 --- a/src/soc/ibm/power9/Makefile.inc +++ b/src/soc/ibm/power9/Makefile.inc @@ -6,7 +6,9 @@ bootblock-y += bootblock.c bootblock-y += rom_media.c romstage-y += rom_media.c romstage-y += romstage.c +romstage-y += mvpd.c romstage-y += vpd.c +romstage-y += powerbus.c romstage-y += istep_13_2.c romstage-y += istep_13_3.c romstage-y += istep_13_4.c @@ -31,5 +33,6 @@ ramstage-y += mvpd.c ramstage-y += vpd.c ramstage-y += tor.c ramstage-y += rs4.c +ramstage-y += powerbus.c endif diff --git a/src/soc/ibm/power9/istep_13_8.c b/src/soc/ibm/power9/istep_13_8.c index cb47e6fc048..b1f9f29853f 100644 --- a/src/soc/ibm/power9/istep_13_8.c +++ b/src/soc/ibm/power9/istep_13_8.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-only */ #include +#include #include #include #include "istep_13_scom.h" #define ATTR_PG 0xE000000000000000ull -#define FREQ_PB_MHZ 1866 /* * This function was generated from initfiles. Some of the registers used here @@ -22,6 +22,8 @@ */ static void p9n_mca_scom(int mcs_i, int mca_i) { + const struct powerbus_cfg *pb_cfg = powerbus_cfg(); + chiplet_id_t id = mcs_ids[mcs_i]; mca_data_t *mca = &mem_data.mcs[mcs_i].mca[mca_i]; const int mca_mul = 0x10; @@ -108,10 +110,15 @@ static void p9n_mca_scom(int mcs_i, int mca_i) [32-39] = (ATTR_PROC_EPS_READ_CYCLES_T2 + 6) / 4 // REMOTE_NODAL_EPSILON [40-47] = (ATTR_PROC_EPS_READ_CYCLES_T2 + 6) / 4 // VECTOR_GROUP_EPSILON */ + #define F(X) (((X) + 6) / 4) scom_and_or_for_chiplet(nest, 0x05010826 + mca_i * mca_mul, ~PPC_BITMASK(0,47), - PPC_SHIFT(1, 7) /* FIXME: fill the rest with non-hardcoded values*/ - | PPC_SHIFT(4, 15) | PPC_SHIFT(4, 23) | PPC_SHIFT(4, 31) - | PPC_SHIFT(0x19, 39) | PPC_SHIFT(0x19, 47)); + PPC_SHIFT(1, 7) + | PPC_SHIFT(F(pb_cfg->eps_r[0]), 15) + | PPC_SHIFT(F(pb_cfg->eps_r[1]), 23) + | PPC_SHIFT(F(pb_cfg->eps_r[1]), 31) + | PPC_SHIFT(F(pb_cfg->eps_r[2]), 39) + | PPC_SHIFT(F(pb_cfg->eps_r[2]), 47)); + #undef F //~ static const uint32_t EPSILON_R_T0_LE[] = { 7, 7, 8, 8, 10, 22 }; // T0, T1 //~ static const uint32_t EPSILON_R_T2_LE[] = { 67, 69, 71, 74, 79, 103 }; // T2 @@ -468,9 +475,10 @@ static void p9n_mca_scom(int mcs_i, int mca_i) /* * From Hostboot: * l_def_mn_freq_ratio = 1000 * ATTR_MSS_FREQ / ATTR_FREQ_PB_MHZ; - * ATTR_MSS_FREQ is in MT/s (sigh), ATTR_FREQ_PB_MHZ is 1866 MHz (from talos.xml). + * ATTR_MSS_FREQ is in MT/s (sigh). */ - uint64_t mn_freq_ratio = 1000 * mem_data.speed / FREQ_PB_MHZ; + uint32_t pb_freq = pb_cfg->fabric_freq; + uint64_t mn_freq_ratio = 1000 * mem_data.speed / pb_freq; uint64_t val_to_data = mn_freq_ratio < 915 ? 3 : mn_freq_ratio < 1150 ? 4 : mn_freq_ratio < 1300 ? 5 : 6; diff --git a/src/soc/ibm/power9/mvpd.c b/src/soc/ibm/power9/mvpd.c index 01bfd84b3a1..2c172a27455 100644 --- a/src/soc/ibm/power9/mvpd.c +++ b/src/soc/ibm/power9/mvpd.c @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -91,57 +92,133 @@ static struct ring_hdr *find_ring(uint8_t chiplet_id, uint16_t ring_id, return NULL; } -/* Finds a specific ring in MVPD partition and extracts it */ -bool mvpd_extract_ring(const char *record_name, const char *kwd_name, - uint8_t chiplet_id, uint16_t ring_id, uint8_t *buf, - uint32_t buf_size) +static const uint8_t *mvpd_get_keyword(const char *record_name, + const char *kwd_name, + size_t *kwd_size, void **mmaped_data) { - const struct region_device *mvpd_device; + const struct region_device *mvpd_device = mvpd_device_ro(); uint8_t mvpd_buf[MVPD_TOC_SIZE]; struct mvpd_toc_entry *mvpd_toc = (struct mvpd_toc_entry *)mvpd_buf; - struct mvpd_toc_entry *cp00 = NULL; - uint16_t cp00_offset = 0; - const uint8_t *cp00_data = NULL; - uint16_t cp00_size = 0; + struct mvpd_toc_entry *toc_entry = NULL; + uint16_t record_offset = 0; + uint8_t *record_data = NULL; + uint16_t record_size = 0; - const uint8_t *rings = NULL; - size_t rings_size = 0; - - struct ring_hdr *ring = NULL; - uint32_t ring_size = 0; - - mvpd_device_init(); - mvpd_device = mvpd_device_ro(); + const uint8_t *kwd = NULL; /* Copy all TOC at once */ if (rdev_readat(mvpd_device, mvpd_buf, 0, sizeof(mvpd_buf)) != sizeof(mvpd_buf)) die("Failed to read MVPD TOC!\n"); - cp00 = find_record(mvpd_toc, record_name); - if (cp00 == NULL) + toc_entry = find_record(mvpd_toc, record_name); + if (toc_entry == NULL) die("Failed to find %s MVPD record!\n", record_name); - cp00_offset = le16toh(cp00->offset); + record_offset = le16toh(toc_entry->offset); /* Read size of the record */ - if (rdev_readat(mvpd_device, &cp00_size, cp00_offset, - sizeof(cp00_size)) != sizeof(cp00_size)) + if (rdev_readat(mvpd_device, &record_size, record_offset, + sizeof(record_size)) != sizeof(record_size)) die("Failed to read size of %s!\n", record_name); - cp00_data = rdev_mmap(mvpd_device, cp00_offset, cp00_size); - if (!cp00_data) + record_data = rdev_mmap(mvpd_device, record_offset, record_size); + if (!record_data) die("Failed to map %s record!\n", record_name); - rings = vpd_find_kwd(cp00_data, record_name, kwd_name, &rings_size); + kwd = vpd_find_kwd(record_data, record_name, kwd_name, kwd_size); + if (kwd == NULL) + die("Failed to find %s keyword in %s!\n", kwd_name, + record_name); + + *mmaped_data = record_data; + return kwd; +} + +bool mvpd_extract_keyword(const char *record_name, const char *kwd_name, + uint8_t *buf, uint32_t *size) +{ + void *mmaped_data = NULL; + + const uint8_t *kwd = NULL; + size_t kwd_size = 0; + bool copied_data = false; + + mvpd_device_init(); + + kwd = mvpd_get_keyword(record_name, kwd_name, &kwd_size, &mmaped_data); + if (kwd == NULL) + die("Failed to find %s keyword in %s!\n", kwd_name, + record_name); + + if (*size >= kwd_size) { + memcpy(buf, kwd, kwd_size); + copied_data = true; + } + + *size = kwd_size; + + if (rdev_munmap(mvpd_device_ro(), mmaped_data)) + die("Failed to unmap %s record!\n", record_name); + + return copied_data; +} + +const struct voltage_kwd *mvpd_get_voltage_data(int lrp) +{ + static int inited_lrp = -1; + static uint8_t buf[sizeof(struct voltage_kwd)]; + + char record_name[] = { 'L', 'R', 'P', '0' + lrp }; + uint32_t buf_size = sizeof(buf); + struct voltage_kwd *voltage = (void *)buf; + + assert(lrp >= 0 && lrp < 6); + if (inited_lrp == lrp) + return voltage; + + inited_lrp = -1; + + if (!mvpd_extract_keyword(record_name, "#V", buf, &buf_size)) { + printk(BIOS_ERR, "Failed to read LRP0 record from MVPD\n"); + return NULL; + } + + if (voltage->version != VOLTAGE_DATA_VERSION) { + printk(BIOS_ERR, "Only version %d of voltage data is supported, got: %d\n", + VOLTAGE_DATA_VERSION, voltage->version); + return NULL; + } + + inited_lrp = lrp; + return voltage; +} + +/* Finds a specific ring in MVPD partition and extracts it */ +bool mvpd_extract_ring(const char *record_name, const char *kwd_name, + uint8_t chiplet_id, uint16_t ring_id, uint8_t *buf, + uint32_t buf_size) +{ + void *mmaped_data = NULL; + + const uint8_t *rings = NULL; + size_t rings_size = 0; + + struct ring_hdr *ring = NULL; + uint32_t ring_size = 0; + + mvpd_device_init(); + + rings = mvpd_get_keyword(record_name, kwd_name, &rings_size, + &mmaped_data); if (rings == NULL) die("Failed to find %s keyword in %s!\n", kwd_name, record_name); ring = find_ring(chiplet_id, ring_id, rings, rings_size); if (ring == NULL) { - if (rdev_munmap(mvpd_device, (void *)cp00_data)) + if (rdev_munmap(mvpd_device_ro(), mmaped_data)) die("Failed to unmap %s record!\n", record_name); return false; @@ -151,7 +228,7 @@ bool mvpd_extract_ring(const char *record_name, const char *kwd_name, if (buf_size >= ring_size) memcpy(buf, ring, ring_size); - if (rdev_munmap(mvpd_device, (void *)cp00_data)) + if (rdev_munmap(mvpd_device_ro(), mmaped_data)) die("Failed to unmap %s record!\n", record_name); return (buf_size >= ring_size); diff --git a/src/soc/ibm/power9/powerbus.c b/src/soc/ibm/power9/powerbus.c new file mode 100644 index 00000000000..96a2cf23d1d --- /dev/null +++ b/src/soc/ibm/power9/powerbus.c @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include + +#include +#include +#include +#include + +#define EPSILON_MAX_VALUE 0xFFFFFFFF + +#define MBOX_SCRATCH_REG1 0x00050038 +#define MBOX_SCRATCH_REG6_GROUP_PUMP_MODE (1 << 23) + +#define EPS_GUARDBAND 20 + +/* From src/import/chips/p9/procedures/hwp/nest/p9_fbc_eff_config.C */ +/* LE epsilon (2 chips per-group) */ +static const uint32_t EPSILON_R_T0_LE[] = { 7, 7, 8, 8, 10, 22 }; +static const uint32_t EPSILON_R_T1_LE[] = { 7, 7, 8, 8, 10, 22 }; +static const uint32_t EPSILON_R_T2_LE[] = { 67, 69, 71, 74, 79, 103 }; +static const uint32_t EPSILON_W_T0_LE[] = { 0, 0, 0, 0, 0, 5 }; +static const uint32_t EPSILON_W_T1_LE[] = { 15, 16, 17, 19, 21, 33 }; + +/* See get_first_valid_pdV_pbFreq() in Hostboot */ + +static bool read_voltage_data(struct powerbus_cfg *cfg) +{ + int i = 0; + const struct voltage_kwd *voltage = NULL; + + /* ATTR_FREQ_PB_MHZ, equal to the first non-zero PowerBus frequency */ + uint32_t pb_freq = 0; + /* ATTR_FREQ_CORE_CEILING_MHZ, equal to the minimum of turbo frequencies */ + uint32_t freq_ceiling = 0; + /* ATTR_FREQ_CORE_FLOOR_MHZ, equal to the maximum of powersave frequencies */ + uint32_t freq_floor = 0; + + /* Using LRP0 because frequencies are the same in all LRP records */ + voltage = mvpd_get_voltage_data(0); + + for (i = 0; i < VOLTAGE_BUCKET_COUNT; ++i) { + const struct voltage_bucket_data *bucket = &voltage->buckets[i]; + if (bucket->id == 0) + continue; + + if (pb_freq == 0 && bucket->powerbus.freq != 0) + pb_freq = bucket->powerbus.freq; + + if (bucket->powersave.freq != 0 && + (freq_floor == 0 || bucket->powersave.freq > freq_floor)) { + freq_floor = bucket->powersave.freq; + } + + if (bucket->turbo.freq != 0 && + (freq_ceiling == 0 || bucket->turbo.freq < freq_ceiling)) { + freq_ceiling = bucket->turbo.freq; + } + } + + cfg->fabric_freq = pb_freq; + cfg->freq_core_floor = freq_floor; + cfg->freq_core_ceiling = freq_ceiling; + + return true; +} + +static bool calculate_frequencies(struct powerbus_cfg *cfg) +{ + const uint32_t pb_freq = cfg->fabric_freq; + const uint32_t freq_floor = cfg->freq_core_floor; + const uint32_t freq_ceiling = cfg->freq_core_ceiling; + + enum FABRIC_CORE_FLOOR_RATIO floor_ratio; + enum FABRIC_CORE_CEILING_RATIO ceiling_ratio; + + /* breakpoint ratio: core floor 4.0, pb 2.0 (cache floor :: pb = 8/8) */ + if (freq_floor >= (2 * pb_freq)) { + floor_ratio = FABRIC_CORE_FLOOR_RATIO_RATIO_8_8; + /* breakpoint ratio: core floor 3.5, pb 2.0 (cache floor :: pb = 7/8) */ + } else if ((4 * freq_floor) >= (7 * pb_freq)) { + floor_ratio = FABRIC_CORE_FLOOR_RATIO_RATIO_7_8; + /* breakpoint ratio: core floor 3.0, pb 2.0 (cache floor :: pb = 6/8) */ + } else if ((2 * freq_floor) >= (3 * pb_freq)) { + floor_ratio = FABRIC_CORE_FLOOR_RATIO_RATIO_6_8; + /* breakpoint ratio: core floor 2.5, pb 2.0 (cache floor :: pb = 5/8) */ + } else if ((4 * freq_floor) >= (5 * pb_freq)) { + floor_ratio = FABRIC_CORE_FLOOR_RATIO_RATIO_5_8; + /* breakpoint ratio: core floor 2.0, pb 2.0 (cache floor :: pb = 4/8) */ + } else if (freq_floor >= pb_freq) { + floor_ratio = FABRIC_CORE_FLOOR_RATIO_RATIO_4_8; + /* breakpoint ratio: core floor 1.0, pb 2.0 (cache floor :: pb = 2/8) */ + } else if ((2 * freq_floor) >= pb_freq) { + floor_ratio = FABRIC_CORE_FLOOR_RATIO_RATIO_2_8; + } else { + printk(BIOS_ERR, "Unsupported core ceiling/PB frequency ratio = (%d/%d)\n", + freq_floor, pb_freq); + return false; + } + + /* breakpoint ratio: core ceiling 4.0, pb 2.0 (cache ceiling :: pb = 8/8) */ + if (freq_ceiling >= (2 * pb_freq)) { + ceiling_ratio = FABRIC_CORE_CEILING_RATIO_RATIO_8_8; + /* breakpoint ratio: core ceiling 3.5, pb 2.0 (cache ceiling :: pb = 7/8) */ + } else if ((4 * freq_ceiling) >= (7 * pb_freq)) { + ceiling_ratio = FABRIC_CORE_CEILING_RATIO_RATIO_7_8; + /* breakpoint ratio: core ceiling 3.0, pb 2.0 (cache ceiling :: pb = 6/8) */ + } else if ((2 * freq_ceiling) >= (3 * pb_freq)) { + ceiling_ratio = FABRIC_CORE_CEILING_RATIO_RATIO_6_8; + /* breakpoint ratio: core ceiling 2.5, pb 2.0 (cache ceiling :: pb = 5/8) */ + } else if ((4 * freq_ceiling) >= (5 * pb_freq)) { + ceiling_ratio = FABRIC_CORE_CEILING_RATIO_RATIO_5_8; + /* breakpoint ratio: core ceiling 2.0, pb 2.0 (cache ceiling :: pb = 4/8) */ + } else if (freq_ceiling >= pb_freq) { + ceiling_ratio = FABRIC_CORE_CEILING_RATIO_RATIO_4_8; + /* breakpoint ratio: core ceiling 1.0, pb 2.0 (cache ceiling :: pb = 2/8) */ + } else if ((2 * freq_ceiling) >= pb_freq) { + ceiling_ratio = FABRIC_CORE_CEILING_RATIO_RATIO_2_8; + } else { + printk(BIOS_ERR, "Unsupported core ceiling/PB frequency ratio = (%d/%d)\n", + freq_ceiling, pb_freq); + return false; + } + + cfg->core_floor_ratio = floor_ratio; + cfg->core_ceiling_ratio = ceiling_ratio; + return true; +} + +static void config_guardband_epsilon(uint8_t gb_percentage, uint32_t *target_value) +{ + uint32_t delta = (*target_value * gb_percentage) / 100; + delta += ((*target_value * gb_percentage) % 100) ? 1 : 0; + + /* Clamp to maximum value if necessary */ + if (delta > (EPSILON_MAX_VALUE - *target_value)) { + printk(BIOS_DEBUG, "Guardband application generated out-of-range target value," + " clamping to maximum value!\n"); + *target_value = EPSILON_MAX_VALUE; + } else { + *target_value += delta; + } +} + +static void dump_epsilons(struct powerbus_cfg *cfg) +{ + uint32_t i; + + for (i = 0; i < NUM_EPSILON_READ_TIERS; i++) + printk(BIOS_DEBUG, " R_T[%d] = %d\n", i, cfg->eps_r[i]); + + for (i = 0; i < NUM_EPSILON_WRITE_TIERS; i++) + printk(BIOS_DEBUG, " W_T[%d] = %d\n", i, cfg->eps_w[i]); +} + +static void calculate_epsilons(struct powerbus_cfg *cfg) +{ + const enum FABRIC_CORE_FLOOR_RATIO floor_ratio = cfg->core_floor_ratio; + const enum FABRIC_CORE_CEILING_RATIO ceiling_ratio = cfg->core_ceiling_ratio; + const uint32_t pb_freq = cfg->fabric_freq; + const uint32_t freq_ceiling = cfg->freq_core_ceiling; + + uint32_t *eps_r = cfg->eps_r; + uint32_t *eps_w = cfg->eps_w; + + uint32_t i; + + uint64_t scratch_reg6 = read_scom(MBOX_SCRATCH_REG1 + 5); + /* ATTR_PROC_FABRIC_PUMP_MODE, it's either node or group pump mode */ + bool node_pump_mode = !(scratch_reg6 & MBOX_SCRATCH_REG6_GROUP_PUMP_MODE); + + /* Assuming that ATTR_PROC_EPS_TABLE_TYPE = EPS_TYPE_LE in talos.xml is always correct */ + + eps_r[0] = EPSILON_R_T0_LE[floor_ratio]; + + if (node_pump_mode) + eps_r[1] = EPSILON_R_T1_LE[floor_ratio]; + else + eps_r[1] = EPSILON_R_T0_LE[floor_ratio]; + + eps_r[2] = EPSILON_R_T2_LE[floor_ratio]; + + eps_w[0] = EPSILON_W_T0_LE[floor_ratio]; + eps_w[1] = EPSILON_W_T1_LE[floor_ratio]; + + /* Dump base epsilon values */ + printk(BIOS_DEBUG, "Base epsilon values read from table:\n"); + dump_epsilons(cfg); + + /* Scale base epsilon values if core is running 2x nest frequency */ + if (ceiling_ratio == FABRIC_CORE_CEILING_RATIO_RATIO_8_8) { + uint8_t scale_percentage = 100 * freq_ceiling / (2 * pb_freq); + if (scale_percentage < 100) + die("scale_percentage is too small!"); + scale_percentage -= 100; + + printk(BIOS_DEBUG, "Scaling based on ceiling frequency\n"); + + for (i = 0; i < NUM_EPSILON_READ_TIERS; i++) + config_guardband_epsilon(scale_percentage, &eps_r[i]); + + for (i = 0; i < NUM_EPSILON_WRITE_TIERS; i++) + config_guardband_epsilon(scale_percentage, &eps_w[i]); + } + + for (i = 0; i < NUM_EPSILON_READ_TIERS; i++) + config_guardband_epsilon(EPS_GUARDBAND, &eps_r[i]); + + for (i = 0; i < NUM_EPSILON_WRITE_TIERS; i++) + config_guardband_epsilon(EPS_GUARDBAND, &eps_w[i]); + + /* Dump final epsilon values */ + printk(BIOS_DEBUG, "Scaled epsilon values based on %s%d percent guardband:\n", + (EPS_GUARDBAND >= 0 ? "+" : "-"), EPS_GUARDBAND); + dump_epsilons(cfg); + + /* + * Check relationship of epsilon counters: + * read tier values are strictly increasing + * write tier values are strictly increasing + */ + if (eps_r[0] > eps_r[1] || eps_r[1] > eps_r[2] || eps_w[0] > eps_w[1]) + printk(BIOS_WARNING, "Invalid relationship between base epsilon values\n"); +} + +const struct powerbus_cfg *powerbus_cfg(void) +{ + static struct powerbus_cfg cfg; + + static bool init_done; + if (init_done) + return &cfg; + + if (!read_voltage_data(&cfg)) + die("Failed to read voltage data"); + + if (!calculate_frequencies(&cfg)) + die("Incorrect core or PowerBus frequency"); + + calculate_epsilons(&cfg); + + init_done = true; + return &cfg; +}