top of page

MicroZed Chronicles: Zynq Power Management – Wake on Interrupt GPIO

One of the projects we are working on now is battery powered and only acts when awakened on a set schedule. This requires the Zynq-7000 device to power down into a low-power state on the SOM in between the awake and sleep periods.. Being battery powered, we want the power to be as low as possible so we will be powering off many peripheral voltage rails including the PL. We also want to put the PS into a low-power sleep state, which is then awakened by an RTC interrupt on a PS GPIO.


Most likely we will use a Pynq or PetaLinux solution on the final development, however, I wanted to know how low the current on the Zynq device could go. Using a MiniZed, USB power monitor, and a simple design in Vivado and Vitis, we can measure the current when sleeping and operating. Of course, we want the application to be able to sleep and then resume the application.


The MiniZed is a good application for this because it has a PS button and a tricolour LED connected to the PL. The Vivado design is created so that the pull up connected to the push button on the PS MIO is disabled. The push button will be high when it is pressed so we want it low otherwise.

The remainder of the PL design is simple and connects the PL LED to the PS using a AXI GPIO. I wanted to use the PL to ensure that we could still communicate and operate with the PL when I restarted the system from its sleeping state and that I had not accidentally disabled anything and forgotten to reenable it.

With this completed, the Vivado project can be built and the XSA exported to Vitis for the embedded software development flow.


The USB monitor I am using is inline and reports the power being taken over USB. It is also Bluetooth capable. This can be connected to a mobile phone and the current profile plotted over the course of the experiment.


When the reset button is pushed, the Zynq device is held in reset and is at its lowest power. At 180 mA, this accounts for the power convertors, DDR, eMMC, and WiFi etc. This also includes the Zynq leakage current.


The software application performs the following steps:


  1. Enables interrupts

  2. Configures the GPIO MIO 0 for interrupts

  3. Enables L2 cache dynamic clock gating

  4. Enables snoop control unit standby mode

  5. Enables topswitch clock stop

  6. Enables dynamic gating

  7. Puts DDR into self-refresh mode

  8. Puts PLL into bypass

  9. Powers down PLL

  10. Slows down the clock

  11. Executes the WFI instruction


The wake up sequency in the interrupt service routine is as follows:

  1. Restore the clock

  2. Power on PLL

  3. Wait for the PLL to lock

  4. Disable PLL bypass mode

  5. Disable L2 cache dynamic clock gating

  6. Disable snoop control unit standby mode

  7. Disable topswitch clock stop

  8. Disable dynamic gating

  9. Restore DDR controller clocks

  10. Resume operation


The code running on my MiniZed can be see below. The PL LED will be toggled once a second when running after the WFI and interrupt has occurred.



#include <stdio.h>
#include "platform.h"
#include "xgpiops.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "unistd.h"
#include "xpseudo_asm.h"
#include "xreg_cortexa9.h"

#define wfi() __asm__("wfi")

#define GPIO_DEVICE_ID		XPAR_XGPIOPS_0_DEVICE_ID
#define INTC_DEVICE_ID		XPAR_SCUGIC_SINGLE_DEVICE_ID
#define GPIO_INTERRUPT_ID	XPS_GPIO_INT_ID

#define l2cpl310_reg15 	0xF8F02F80
#define scu_control_reg    0xF8F00000
#define topsw_clk 		0xF800016C
#define slcr_unlock 	     0xF8000008
#define ddrc_ctrl_reg1 	0xF8006060
#define ddrc_para_reg3     0xF8006020
#define ddr_clk_ctrl 	    0xF8000124
#define dci_clk_ctrl 	    0xF8000128
#define arm_pll_ctrl 	    0xF8000100
#define ddr_pll_ctrl 	    0xF8000104
#define io_pll_ctrl  	    0xF8000108
#define arm_clk_ctrl 	    0xF8000120
#define gpio_int_en0 	    0xE000A210
#define pll_status 	    0xE800010C
#define uart_sel	        0xF8000154
#define aper_reg          0xF800012C

#define ledpin 52
#define pbsw 0

static XScuGic Intc; /* The Instance of the Interrupt Controller Driver */
static XGpioPs Gpio; /* The driver instance for GPIO Device. */
int toggle = 0;//used to toggle the LED
static void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId);
static void IntrHandler(void *CallBackRef, int Bank, u32 Status);

int main()
{
	int Status;

	XGpioPs_Config *GPIOConfigPtr;

	init_platform();

    printf("low power example\n\r");

    //GPIO Initilization
    GPIOConfigPtr = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_DEVICE_ID);
    Status = XGpioPs_CfgInitialize(&Gpio, GPIOConfigPtr,GPIOConfigPtr->BaseAddr);
    	if (Status != XST_SUCCESS) {
    		 print("GPIO INIT FAILED\n\r");
    		return XST_FAILURE;
    	}

    //set direction and enable output
    XGpioPs_SetDirectionPin(&Gpio, ledpin, 1);
    XGpioPs_SetOutputEnablePin(&Gpio, ledpin, 1);
    
    //set direction input pin
    XGpioPs_SetDirectionPin(&Gpio, pbsw, 0x0)
    SetupInterruptSystem(&Intc, &Gpio, GPIO_INTERRUPT_ID);
	u32 data;
	XGpioPs_WritePin(&Gpio, ledpin, 0);
	Xil_Out32(slcr_unlock,0xDF0D);//unlock SLCR registers 

	data = Xil_In32(aper_reg);  //clock gate unused peripherals
	printf("aper_reg = %x\n\r", (unsigned int) data);
	data = 0x1600001;
	Xil_Out32(aper_reg,data);
	data = Xil_In32(aper_reg);
	printf("aper_reg = %x\n\r", (unsigned int) data);

	data = Xil_In32(arm_clk_ctrl);  //clock gate unused peripherals
	printf("arm_clk_ctrl = %x\n\r", (unsigned int) data);

	data = Xil_In32(uart_sel);
	printf("uart_sel = %x\n\r", (unsigned int) data);
	usleep(100);
	data = Xil_In32(arm_clk_ctrl);
	printf("arm_clk_ctrl = %x\n\r", (unsigned int) data);
	data = Xil_In32(gpio_int_en0);
	data = 0x00000400;

	//configure cache for low power mode
	data = Xil_In32(l2cpl310_reg15);
	printf("ls cache reg 15 power = %x\n\r", (unsigned int) data);
	data = 0x3; //enable standby mode and dynamic clock gating
	Xil_Out32(l2cpl310_reg15,data);
	data = Xil_In32(l2cpl310_reg15);
	printf("ls cache reg 15 power = %x\n\r", (unsigned int) data);

	//configure scu
	data = Xil_In32(scu_control_reg);
	printf("scu control reg = %x\n\r", (unsigned int) data);
	data |=  0x00000020; //enable standby mode and dynamic clock gating
	Xil_Out32(scu_control_reg,data);
	data = Xil_In32(scu_control_reg);
	printf("scu control reg = %x\n\r", (unsigned int) data);

	//configure slcr
	data = Xil_In32(topsw_clk);
	printf("slcr control reg = %x\n\r", (unsigned int) data);
	data |=  0x00000001; //enable standby mode and dynamic clock gating
	Xil_Out32(topsw_clk,data);
	data = Xil_In32(topsw_clk);
	printf("slcr control reg = %x\n\r", (unsigned int) data);
	XGpioPs_WritePin(&Gpio, ledpin, 1);

	//set CP15
	data = mfcp(XREG_CP15_POWER_CTRL);
	printf("cp15 Reg = %x\n\r", (unsigned int) data);
	mtcp(XREG_CP15_POWER_CTRL,0x701);
	data = mfcp(XREG_CP15_POWER_CTRL);
	printf("cp15 Reg = %x\n\r", (unsigned int) data);

	//set up ddr
	data = Xil_In32(ddrc_ctrl_reg1);
	data |= 0x00001000; //enable standby mode and dynamic clock gating
	Xil_Out32(ddrc_ctrl_reg1,data);
	
	data = Xil_In32(ddrc_para_reg3);
	data |= 0x00100000; //enable standby mode and dynamic clock gating
	Xil_Out32(ddrc_para_reg3,data);
	
	data = Xil_In32(ddr_clk_ctrl);
	data &= 0xFFFFFFF0; //enable standby mode and dynamic clock gating
	Xil_Out32(ddr_clk_ctrl,data);
	
	data = Xil_In32(dci_clk_ctrl);
	data &= 0xFFFFFFF0; //enable standby mode and dynamic clock gating
	Xil_Out32(dci_clk_ctrl,data);
	
	//	arm_pll_ctrl
	data = Xil_In32(arm_pll_ctrl);
	data |= 0x00000010; //enable standby mode and dynamic clock gating
	Xil_Out32(arm_pll_ctrl,data);
	
	data = Xil_In32(ddr_pll_ctrl);
	data |= 0x00000010; //enable standby mode and dynamic clock gating
	Xil_Out32(ddr_pll_ctrl,data);
	
	data = Xil_In32(io_pll_ctrl);
	data |= 0x00000010; //enable standby mode and dynamic clock gating
	Xil_Out32(io_pll_ctrl,data);
	
	data = Xil_In32(arm_pll_ctrl);
	data |= 0x00000002; //enable standby mode and dynamic clock gating
	Xil_Out32(arm_pll_ctrl,data);
	
	data = Xil_In32(ddr_pll_ctrl);
	data |= 0x00000002; //enable standby mode and dynamic clock gating
	Xil_Out32(ddr_pll_ctrl,data);
	
	data = Xil_In32(io_pll_ctrl);
	data |= 0x00000002; //enable standby mode and dynamic clock gating
	Xil_Out32(io_pll_ctrl,data);
	
	data = Xil_In32(arm_clk_ctrl);
	data |= 0x0003f00; //enable standby mode and dynamic clock gating
	Xil_Out32(arm_clk_ctrl,data);

	wfi();

	while(1){

	toggle = !toggle;
	XGpioPs_WritePin(&Gpio, ledpin, toggle);
	//printf("hello world\n\r");
	usleep(1000000);

    }

    return 0;
}

static void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId)
{


		XScuGic_Config *IntcConfig; 
		Xil_ExceptionInit();

		IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);

		XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
						IntcConfig->CpuBaseAddress);

		Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
					(Xil_ExceptionHandler)XScuGic_InterruptHandler,
					GicInstancePtr);

		XScuGic_Connect(GicInstancePtr, GpioIntrId,
					(Xil_ExceptionHandler)XGpioPs_IntrHandler,
					(void *)Gpio);

		//Enable  interrupts for all the pins in bank 1.
		XGpioPs_SetIntrTypePin(Gpio, pbsw, XGPIOPS_IRQ_TYPE_LEVEL_HIGH);

		//Set the handler for gpio interrupts.
		XGpioPs_SetCallbackHandler(Gpio, (void *)Gpio, IntrHandler);
		XGpioPs_IntrClear(Gpio, 1,0xFFFFFFFF);
		//Enable the GPIO interrupts of Bank 1.
		XGpioPs_IntrEnablePin(Gpio, pbsw);

		//Enable the interrupt for the GPIO device.
		XScuGic_Enable(GicInstancePtr, GpioIntrId);

		// Enable interrupts in the Processor.
		Xil_ExceptionEnableMask(XIL_EXCEPTION_ALL);
	}

static void IntrHandler(void *CallBackRef, int Bank, u32 Status)
{
	u32 ddrc;
	u32 data;

	data = 0x1f000200;
	Xil_Out32(arm_clk_ctrl,data);

	Xil_Out32(aper_reg,0x01fc0c0d);

	ddrc = Xil_In32(io_pll_ctrl);
	ddrc &= ~0x00000002; //remove standby mode and dynamic clock gating
	Xil_Out32(io_pll_ctrl,ddrc);

	ddrc = Xil_In32(ddr_pll_ctrl);
	ddrc &= ~0x00000002; //remove standby mode and dynamic clock gating
	Xil_Out32(ddr_pll_ctrl,ddrc);

	ddrc = Xil_In32(arm_pll_ctrl);
	ddrc &= ~0x00000002; //remove standby mode and dynamic clock gating
	Xil_Out32(arm_pll_ctrl,ddrc);
	
	ddrc = Xil_In32(pll_status);
	while(ddrc != 0x0000003f){ //wait for DLL to lock and be stable
		ddrc = Xil_In32(pll_status);
	}

	ddrc = Xil_In32(arm_pll_ctrl);
	ddrc &= ~0x00000010; //enable standby mode and dynamic clock gating
	Xil_Out32(arm_pll_ctrl,ddrc);

	
	ddrc = Xil_In32(ddr_pll_ctrl);
	ddrc &= ~0x00000010; //enable standby mode and dynamic clock gating
	Xil_Out32(ddr_pll_ctrl,ddrc);
	ddrc = Xil_In32(ddr_pll_ctrl);
	
	ddrc = Xil_In32(io_pll_ctrl);
	ddrc &= ~0x00000010; //enable standby mode and dynamic clock gating
	Xil_Out32(io_pll_ctrl,ddrc);


	Xil_Out32(slcr_unlock,0xDF0D);

	//configure slcr
	data = Xil_In32(topsw_clk);
	data &=  ~0x00000001; //enable standby mode and dynamic clock gating
	Xil_Out32(topsw_clk,data);
	XGpioPs_WritePin(&Gpio, ledpin, 0);

	//set CP15
	data = mfcp(XREG_CP15_POWER_CTRL);
	
	mtcp(XREG_CP15_POWER_CTRL,0x700);
	data = mfcp(XREG_CP15_POWER_CTRL);
	
	data = Xil_In32(ddrc_ctrl_reg1);
	data &= ~0x00001000; //enable standby mode and dynamic clock gating
	Xil_Out32(ddrc_ctrl_reg1,data);

	data = Xil_In32(ddrc_para_reg3);
	data &= ~0x00100000; //enable standby mode and dynamic clock gating
	Xil_Out32(ddrc_para_reg3,data);

	data = Xil_In32(ddr_clk_ctrl);
	data |= 0x00000003; //enable standby mode and dynamic clock gating
	Xil_Out32(ddr_clk_ctrl,data);

	data = Xil_In32(dci_clk_ctrl);
	data |= 0x00000003; //enable standby mode and dynamic clock gating
	Xil_Out32(dci_clk_ctrl,data);

	XGpioPs_IntrDisablePin(&Gpio, pbsw);
	printf("****button pressed****\n\r");

	XGpioPs_IntrClear(&Gpio, 1,0xFFFFFFFF); 
	XGpioPs_IntrEnablePin(&Gpio, pbsw);
}

The application in Vitis is designed to execute from the DDR. This ensures that we get the DDR clocking and self-refreshing correct. We will not be able to easily resume operation of the program if it is not correctly configured.


The software application is design to put the processor to sleep. Issue the WFI and wait for the PB switch to be pressed to wake up the processor and resume processing.


When the application is running, the board is taking 390 mA. This drops to 220 mA while the processor is sleeping. When the push button is pressed, the interrupt is generated and the processor reawakens thereby restoring the current to about 390 mA again.

Once the processor wakes, it continues to execute the application and the LED continues to flash on the MiniZed once a second.


It is good that we can save 170 mA by putting the Zynq device to sleep. However, the battery-powered application needs to be able to power down many more of its peripherals in order to have a battery life which extends for the time defined in the requirements

0 comments
bottom of page