The high-level overview of the software implementation is depicted in the following figure.
The implementation requires the PMC I2C/PMBus controller to be configured to drive the I2C0 bus on the VCK190 board. Upon parsing the policy CDO, as shown in the figure, a cyclic task is scheduled to be run every 100 ms. At each invocation, the content of the DEVICE_TEMP_MAX register is obtained through the SYSMON driver.
This temperature reading is compared with the threshold values specified in the policy CDO and the current state of power mode. The hysteresis algorithm is used to determine when to increase the voltage when crossing the lower threshold and when to decrease the voltage when crossing the upper threshold.
UpperTempThresh = Rail->TempVoltAdj->UpperTempThresh;
LowerTempThresh = Rail->TempVoltAdj->LowerTempThresh;
UpperVoltMode = Rail->TempVoltAdj->UpperVoltMode;
LowerVoltMode = Rail->TempVoltAdj->LowerVoltMode;
CurrentVoltMode = &Rail->TempVoltAdj->CurrentVoltMode;
/* Validate that the argument passed in is a power rail */
if ((u32)XPM_NODETYPE_POWER_RAIL != NODETYPE(Rail->Power.Node.Id)) {
Status = XST_INVALID_PARAM;
goto done;
}
/*
* If Root SysMon is not initialized yet, skip the cycle until
* it is initialized.
*/
if (0U == SysMonInstPtr->Config.BaseAddress) {
Status = XST_SUCCESS;
goto done;
}
/* Read DEVICE_TEMP_MAX register through SysMon driver */
CurrentTemp = XSysMonPsv_ReadDeviceTemp(SysMonInstPtr, XSYSMONPSV_VAL);
/*
* If the current temperature is at or below lower threshold and
* we are not in upper voltage mode, make an adjustment to higher
* voltage. Similarly, if the temperature is at or above upper
* threshold and not in lower voltage mode, make an adjustment
* to lower voltage. If the temperature is between lower and upper
* threshold, no adjustment is needed.
*/
if ((XSysMonPsv_FixedToFloat(CurrentTemp) <=
XSysMonPsv_FixedToFloat(LowerTempThresh)) &&
(*CurrentVoltMode != UpperVoltMode)) {
PmDbg("Current temperature is 0x%x, Set voltage to upper mode "
"%d\n\r", CurrentTemp, UpperVoltMode);
Status = XPmRail_Control(Rail, (u8)XPM_POWER_STATE_ON,
UpperVoltMode);
if (XST_SUCCESS != Status) {
DbgErr = XPM_INT_ERR_RAIL_UPPER_VOLT;
goto done;
}
*CurrentVoltMode = UpperVoltMode;
} else if ((XSysMonPsv_FixedToFloat(CurrentTemp) >=
XSysMonPsv_FixedToFloat(UpperTempThresh)) &&
(*CurrentVoltMode != LowerVoltMode)) {
PmDbg("Current temperature is 0x%x, Set voltage to lower mode "
"%d\n\r", CurrentTemp, LowerVoltMode);
Status = XPmRail_Control(Rail, (u8)XPM_POWER_STATE_ON,
LowerVoltMode);
if (XST_SUCCESS != Status) {
DbgErr = XPM_INT_ERR_RAIL_LOWER_VOLT;
goto done;
}
*CurrentVoltMode = LowerVoltMode;
} else if ((XSysMonPsv_FixedToFloat(CurrentTemp) <
XSysMonPsv_FixedToFloat(UpperTempThresh)) &&
(XSysMonPsv_FixedToFloat(CurrentTemp) >
XSysMonPsv_FixedToFloat(LowerTempThresh)) &&
(0U == *CurrentVoltMode)) {
PmDbg("Current temperature is 0x%x, Set voltage to lower mode "
"%d\n\r", CurrentTemp, LowerVoltMode);
Status = XPmRail_Control(Rail, (u8)XPM_POWER_STATE_ON,
LowerVoltMode);
if (XST_SUCCESS != Status) {
DbgErr = XPM_INT_ERR_RAIL_LOWER_VOLT;
goto done;
}
*CurrentVoltMode = LowerVoltMode;
} else {
Status = XST_SUCCESS;
}
When adjusting the voltage based on the selected power mode, a series of I2C commands are issued to the power regulator. The relevant source code can be found in the embeddedsw GitHub repository files (lib/sw_services/xilpm/src/versal/server/xpm_rail.c and lib/sw_services/xilpm/src/versal/server/xpm_rail.h).
The following code snippet implements this functionality. This compares the current temperature with lower and upper temperature thresholds. If it is at or below a lower threshold and currently not at the upper voltage level, it adjusts the voltage to a higher value. If it is at or above an upper threshold and currently not at the lower voltage level, it adjusts the voltage to a lower value. If it is within lower and upper threshold boundaries, no voltage adjustment is made.
/*
* This routine is invoked periodically by the task scheduler and its
* task is to determine if a voltage adjustment is needed, based on current
* temperature and current voltage level. The argument passed points to power
* rail that needs to be adjusted.
*/
static int XPmRail_CyclicTempVoltAdj(void *Arg)
{
int Status = XST_FAILURE;
u16 DbgErr = XPM_INT_ERR_UNDEFINED;
XPm_Rail *Rail = (XPm_Rail *)Arg;
u32 UpperTempThresh, LowerTempThresh;
u8 UpperVoltMode, LowerVoltMode;
u32 CurrentTemp;
u8 *CurrentVoltMode;
XSysMonPsv *SysMonInstPtr = XPlmi_GetSysmonInst();
UpperTempThresh = Rail->TempVoltAdj->UpperTempThresh;
LowerTempThresh = Rail->TempVoltAdj->LowerTempThresh;
UpperVoltMode = Rail->TempVoltAdj->UpperVoltMode;
LowerVoltMode = Rail->TempVoltAdj->LowerVoltMode;
CurrentVoltMode = &Rail->TempVoltAdj->CurrentVoltMode;
/* Validate that the argument passed in is a power rail */
if ((u32)XPM_NODETYPE_POWER_RAIL != NODETYPE(Rail->Power.Node.Id)) {
Status = XST_INVALID_PARAM;
goto done;
}