diff options
Diffstat (limited to 'src/lib/heartbeat.c')
-rwxr-xr-x | src/lib/heartbeat.c | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/src/lib/heartbeat.c b/src/lib/heartbeat.c new file mode 100755 index 0000000..51be390 --- /dev/null +++ b/src/lib/heartbeat.c @@ -0,0 +1,328 @@ +// $Id: heartbeat.c,v 1.5 2014/07/16 18:07:35 daviddu Exp $ +// $Source: /afs/awd/projects/eclipz/KnowledgeBase/.cvsroot/eclipz/chips/p8/working/procedures/lib/heartbeat.c,v $ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2013 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file heartbeat.c +/// \brief PgP PMC/PCBS heartbeat configuration procedures + +#include "ssx.h" +#include "heartbeat.h" + +/// Configure/Enable/Disable the pmc heartbeat register. +/// +/// \param enable 1 = enable, 0 = disable, all other values will cause error. +/// +/// \param req_time_us heartbeat interval time request (in microseconds). +/// If the pmc does not detect a heartbeat within this time the pmc will +/// set the corresponding fir bit and enter safe mode. This interval +/// is the requested value. The return value will be the actual setting. +/// The procedure well attempt to get as close to the requested time as possible +/// without choosing a setting lower then requested. +/// Legal values: 1-4194240 (us). Ignored if force = 1 or enable = 0 +/// +/// \param force 1 = force safe mode (debug), 0 = do not force, all other values +/// will cause an error. enable = 0 and force = 1 will return an error +/// +/// \param[out] o_time_us Actual configured time rounded down to the nearest us. +/// This will be as close as the procedure could get to the requested time given +/// the frequency and pulse time settings. Returns 0 if hearbeat was disabled or +/// if safe mode was forced. +/// +/// \retval 0 Success +/// +/// \retval -HB_INVALID_ARGUMENT_PMC One of the arguments was invalid in +/// some way + +int +pmc_hb_config(unsigned int enable, + unsigned int req_time_us, + unsigned int force, + unsigned int *o_time_us + ) + +{ + pmc_parameter_reg0_t ppr0; + pmc_occ_heartbeat_reg_t pohr; + tpc_hpr2_t l_hpr2; + uint64_t divider, pulses, total_pulses, hp_freq; + int rc = 0; + + // @dyd SW238882 fix + // remove req_time_us overflow check since the upper boundary of + // the req_time_us doesnt depand on certain static value but based on + // the value set in hang_pulse_2_reg at runtime. + if (SSX_ERROR_CHECK_API) { + SSX_ERROR_IF((enable > 1) || + (force > 1) || + ((req_time_us < 1) && enable && (! force)) || + ((force == 1 && enable == 0)), + HB_INVALID_ARGUMENT_PMC); + } + + do { + + // in case firmware does not call ocb_timer_setup + // before calling this procedure to setup g_ocb_timer_divider + rc = getscom(TPC_HPR2, &l_hpr2.value); + if(rc) break; + g_ocb_timer_divider = 1 << l_hpr2.fields.hang_pulse_reg; + + // calculation based on pmc_occ_heartbeat_reg defination + hp_freq = (__ssx_timebase_frequency_mhz/g_ocb_timer_divider); + if(hp_freq < 1) + hp_freq = 1; + total_pulses = (req_time_us * hp_freq); + + // this may be an overkill for safety but no one should notice + if ((req_time_us*__ssx_timebase_frequency_mhz) % g_ocb_timer_divider) { + total_pulses++; + } + + divider = 0; + // determine values for predivider and number of pulses. + if (force || (! enable)) { // predivider a don't care in this case + pulses = 0; + *o_time_us = 0; + } else { + // can count up to 2^16 pulses with no pre-divide, first determine + // minimum pre-divider needed + do { + divider++; + } while ((((divider << 16) - 1) / total_pulses) < 1); + + // @dyd SW238882 fix + // underflow case + // since pmc heartbeat counter counts with nest_nclk/4 + // instead of hang pulse when hangpulse_predivider==0, + // this procedure doesnt allow predivider to be set to + // zero as it is a special case which doesnt work with + // occ heartbeat time value calculated by this procedure. + // Given hangpulse_predivider = divider - 1, + // set divider to 2 if it is 1, zero not possible. + if (divider < 2) { + divider = 2; + //rc = HB_UNDERFLOW_DIVIDER_PMC; + //break; + } + // overflow case + // since hangpulse_predivider field is only 6 bit long, + // check the overflow first, set to maximum if larger. + if (divider > 64) { + divider = 64; + //rc = HB_OVERFLOW_DIVIDER_PMC; + //break; + } + + // divider is determined, now setup number of pulses + pulses = total_pulses / divider; + if (total_pulses % divider) { + pulses++; + } + + // @dyd SW238882 fix + // there is no underflow case for pulses, because pulses=0 as + // intended immediate timeout is allowed, plus no mathematical + // substraction from pulses is done; however there is an overflow + // case: the value of pulses doesnt fit into 16 bits HW field. + // Here we set pulses to the maximum value that HW allows, + // and use the o_time_us to feedback the caller this is done. + if (pulses > 0xFFFF) { + pulses = 0xFFFF; + //rc = HB_OVERFLOW_PULSES_PMC; + // break; + } + + // calculating real timeout duration + // that this procedure is going to set + *o_time_us = (divider*pulses)/hp_freq; + + // @dyd SW238882 fix + // in force == 0 && enable == 1 case + // disable heartbeat first before reset hang pulse predivider + // and new heartbeat time value to prevent immediate timeout. + // if force == 1 then it is intended to be immediate timeout anyway + // if enable == 0 then it is going to set this bit to zero anyway + pohr.value = 0; + pohr.fields.pmc_occ_heartbeat_en = 0; + if (cfam_id() == CFAM_CHIP_ID_MURANO_10) { + out32(PMC_OCC_HEARTBEAT_REG, pohr.value); + } + out32(PMC_OCC_HEARTBEAT_REG, pohr.value); + } + + // Note through experiments, the divider=predivider+1 isnt always + // in effect in hardware due to missing last pulse in tight timing, + // some setup will end up with just divider=predivider; therefore, + // in order to not result unexpected heartbeat timeout, always + // set divider to predivider to be safe. + if (enable && (! force)) { + ppr0.value = in32(PMC_PARAMETER_REG0); + ppr0.fields.hangpulse_predivider = divider; + out32(PMC_PARAMETER_REG0, ppr0.value); + } + + pohr.value = 0; + pohr.fields.pmc_occ_heartbeat_en = enable; + pohr.fields.pmc_occ_heartbeat_time = pulses; + // Due to Issue HW219480, the heartbeat register needs to be written + // Twice in order for the heartbeat count value to take correctly. + // Technically it would not be harmful to just double-write in + // every case, but this is currently written to only double-write + // if a Murano dd1.0 part is detected + if (cfam_id() == CFAM_CHIP_ID_MURANO_10) { + out32(PMC_OCC_HEARTBEAT_REG, pohr.value); + } + out32(PMC_OCC_HEARTBEAT_REG, pohr.value); + + }while(0); + return rc; + +} + + + +/// Configure/Enable/Disable the pcbs heartbeat registers. +/// +/// \param enable 1 = enable, 0 = disable, all other values will cause error. +/// +/// \param cores Use this mask to select which cores to update. This routine +/// will cross reference the current pmc deconfig vector and only update +/// those cores that are both selected here and configured. +/// +/// \param hb_reg 32-bit unsigned address of register to setup as the +/// PCBS heartbeat register. This must be a PCBS address. +/// Ignored unless enable = 1 +/// +/// \param req_time_us heartbeat interval time request (in microseconds). +/// If the pcbs does not detect a heartbeat within this time the pcbs will +/// set the corresponding fir bit and enter safe mode. This interval +/// is the requested value. The return value will be the actual setting and +/// the procedure will attempt go get as close to possible to this without +/// choosing a setting lower then requested. +/// Legal values: 1 - 16320 (ignored unless enable = 1) +/// +/// \param force 1 = force safe mode (debug), 0 = do not force, all other values +/// will cause an error. In PCBS, the force safe mode is not related to +/// the heartbeat so forcing safe mode while also enabling the heartbeat +/// is allowed. +/// +/// \param[out] o_time_us Actual configured time in us. This represents the +/// actual setting rounded down to the nearest us. 0 if heartbeat was disabled. +/// +/// \retval 0 Success + +/// \retval -HB_INVALID_ARGUMENT_PCBS One of the arguments was invalid in +/// some way +/// +/// \retval others This API may also return non-0 codes from +/// getscom()/putscom() + + +int +pcbs_hb_config(unsigned int enable, + ChipConfigCores cores, + uint32_t hb_reg, + unsigned int req_time_us, + unsigned int force, + unsigned int *o_time_us) +{ + pcbs_occ_heartbeat_reg_t pohr; + pcbs_pmgp1_reg_t pp1r; + pmc_core_deconfiguration_reg_t pcdr; + uint32_t reg_offset; + uint32_t pp1r_addr; + uint64_t pp1r_data; + ChipConfigCores core_list; + ChipConfigCores deconfig; + int core; + int rc = 0; + unsigned int pulses; + + reg_offset = hb_reg - PCBS_PIB_BASE; + + if (SSX_ERROR_CHECK_API) { + SSX_ERROR_IF((enable > 1) || + ((reg_offset > 0xFF) && enable) || + ((req_time_us < 64) && enable) || + ((req_time_us > 16320) && enable) || + (force > 1), + HB_INVALID_ARGUMENT_PCBS); + } + + + do { + + // calculation based on pcbs_occ_heartbeat_reg defination + pulses = req_time_us/64; + if (req_time_us % 64) { + pulses++; + } + + // @dyd SW238882 fix + // overflow handling: HW only allows 8 bits in the field. + // set pulses to maximum allowed value in HW if it overflows, + // and o_time_us will feedback to caller this is done. + if (pulses > 0xFF) { + pulses = 0xFF; + //rc = HB_PULSES_OVERFLOW_PCBS; + //break; + } + // underflow case, pulses cannot be zero due to undefined HW behavior + if (pulses < 1) { + pulses = 1; + //rc = HB_PULSES_UNDERFLOW_PCBS; + //break; + } + + pp1r.value = 0; + pp1r.fields.force_safe_mode = 1; + if (force) { + pp1r_addr = PCBS_PMGP1_REG_OR; + pp1r_data = pp1r.value; + } else { + pp1r_addr = PCBS_PMGP1_REG_AND; + pp1r_data = ~(pp1r.value); + } + + pcdr.value = in32(PMC_CORE_DECONFIGURATION_REG); + deconfig = pcdr.fields.core_chiplet_deconf_vector; + + pohr.value = 0; + pohr.fields.occ_heartbeat_enable = enable; + pohr.fields.occ_heartbeat_time = pulses; + pohr.fields.occ_heartbeat_reg_addr_offset = reg_offset; + + if (enable) { + *o_time_us = pulses * 64; + } else { + *o_time_us = 0; + } + + do { + core_list = cores & (~deconfig); + for (core = 0; core < PGP_NCORES; core++, core_list <<= 1) { + if (core_list & 0x8000) { + // read modify write to preserve psafe + rc = getscom(CORE_CHIPLET_ADDRESS(PCBS_OCC_HEARTBEAT_REG, + core), &pohr.value); + if (rc) break; + pohr.fields.occ_heartbeat_enable = enable; + pohr.fields.occ_heartbeat_time = pulses; + pohr.fields.occ_heartbeat_reg_addr_offset = reg_offset; + rc = putscom(CORE_CHIPLET_ADDRESS(PCBS_OCC_HEARTBEAT_REG, + core), pohr.value); + if (rc) break; + rc = putscom(CORE_CHIPLET_ADDRESS(pp1r_addr, core), + pp1r_data); + if (rc) break; + } + } + } while (0); + + }while(0); + return rc; +} |